Inspiration

A few weeks ago I caught myself at 1am, doomscrolling rage-bait I never asked for.

I feed that algorithm every click — every dwell, every scroll-past, every expand. I generate 100% of the signal. And what do I get back? A black box I can't see, can't change, and can't take with me. It optimizes for their engagement, not my wellbeing. So it feeds me outrage, not the indie-dev threads and recipes I actually linger on.

"For You" was never for you.

So I stopped renting my algorithm. I grew my own.

What it does

Sourdough is a personal recommendation "starter" that lives only on your device. Like a real sourdough culture, it's something you feed over time and never hand to a stranger.

It ships as a real Manifest V3 browser extension that re-ranks your live Hacker News feed in place — and as a web/PWA demo of the same engine. The mechanic is the one your "For You" feeds use, turned around to serve you. It:

  • Re-ranks your feed for YOU, live. Your taste rises to the top with a golden "rising dough" glow; the rage-bait sinks and dims.
  • Tells you why — every single card. A plain-language line ("rose: more of the indie-dev + cooking threads you linger on"), and if you tap it, a breakdown of every feature contribution that adds up to the exact score. Glass box, not black box.
  • Learns when you talk to it. Type "less doomscroll, more long-form cooking" and watch a real scorer weight physically slide — a gradient step you can see, not a guess.
  • Forks per mood. A "Work" starter, a "Sunday" starter — separate cultures for separate headspaces.
  • Is yours to keep. Export your whole taste as one versioned, integrity-checked .dough file. Re-import on a fresh browser and the feed re-ranks correctly on the first scroll. Cold-start, solved by ownership.
  • Proves its privacy on camera. A live counter shows network calls out: 0 — a real tally of every outbound call (fetch/XHR/sendBeacon/WebSocket), not a hard-coded zero.

The same read-side adapter is built to generalize to the feeds where "social media" actually lives — an X / Reddit / TikTok-style "For You" — so the idea is one recommendation mechanic you own, on any feed you read.

How I built it

The architecture is one honest line, all client-side:

capture → embed (cached) → score (online LR) → explain (per-card "why") → render (FLIP)

On the live site, the extension's content script reads the real Hacker News DOM into that same Post shape (real title, domain, points, comments, true age), so the identical engine re-ranks your actual feed in place; the web demo swaps in a bundled snapshot so the before/after and the network: 0 proof are perfectly reproducible.

  • Capture implicit feedback (dwell via IntersectionObserver, scroll-past, expand) locally — never a server. (The demo starter ships pre-warmed with an exaggerated taste profile — true to a culture "you've been feeding for weeks" — so the before/after pops; only the live ticks during the demo are freshly captured.)
  • Embed each post on-device into a 256-d vector. The demo uses a deterministic hashing-trick embedder (token + character-trigram features, L2-normalised) so it's instant, reproducible, and truly zero-network; it sits behind an interface designed to swap in quantized all-MiniLM-L6-v2 on WebGPU unchanged.
  • Score with the one genuinely-learned component: an online logistic-regression ranker over seven interpretable features (taste-similarity, dwell, long-form, source-affinity, expand-rate, recency, comment-velocity). A typical post scores exactly 0.50, and baseline + Σ(contributions) = score, so the per-card "why" reconciles to the number. Re-ranking the whole page is synchronous and sub-millisecond — comfortably inside the <16ms/frame budget for 60fps scrolling.
  • Learn from your feedback and steers with a real SGD step (wₖ += lr·(y − σ(z))·xₖ) — which is why a weight visibly moves on screen.
  • Steer by parsing your free text into structured taste-rules on-device, fully deterministically. (I feature-detect Chrome's built-in Gemini Nano / Prompt API for a future enrichment path, but the shipped parser never needs it — so the demo can't be broken by a flaky primitive.)
  • Render with a true FLIP animation (transform + opacity only, 60fps) so the eye literally sees the dough rise.
  • Own it with a versioned .dough file, content-hashed with SHA-256 (Web Crypto, on-device) — an integrity check that catches corruption, no server, no account.

Built framework-free in TypeScript + Vite, shipped as an offline PWA. No backend, no API keys, no account — and a live counter that proves zero calls out.

Challenges I ran into

  • The latency contradiction. On-device explanation can't block 60fps scrolling. I resolved it by architecture: scoring stays synchronous on the hot path; the per-card "why" is decoupled and templated from the feature contributions, so the two never fight.
  • "Is the ML real, or cosine theater?" I committed to one genuinely-learned component (online LR with a real SGD step) and made its learning visible — a weight moves after your correction, and you can expand any card to audit the contribution math that sums to the score.
  • Making network: 0 provable, not asserted. I wrapped fetch/XHR/sendBeacon/WebSocket and show the live count. If anything ever tried to call out, you'd watch it tick.
  • Making a re-order feel like magic. A list that just shuffles is boring. The FLIP "rising dough" motion + golden glows + a hard-staged before/after is what turns a sort into a wow.

Accomplishments I'm proud of

Getting a real online-LR weight to visibly move from a single click, on a synchronous hot path, with a live counter reading zero calls out — and a per-card breakdown whose bars literally add up to the score. The hard part is the honesty, and you can watch all of it happen.

What I learned

The most convincing way to answer "is this real?" is to make the hard part visible — the moving weight, the reconciling bars, the live zero. Transparency isn't just the ethics of the project; it's the best demo.

What's next

Swap the demo embedder for WebGPU all-MiniLM behind the same interface; generalize the adapter beyond Hacker News so you can re-rank any social feed you read; and let people share starters as portable taste-profiles.

"For You" was never for you. Sourdough is.

Built With

  • browser-extension
  • chrome-storage
  • content-scripts
  • css3
  • html5
  • intersection-observer
  • javascript
  • localstorage
  • logistic-regression
  • machine-learning
  • manifest-v3
  • on-device-ai
  • pwa
  • recommender-systems
  • service-workers
  • text-embeddings
  • typescript
  • vite
  • web-crypto
  • workbox
Share this project:

Updates