lmjtfy.git / apps / lmjtfy

Chapter 2: the Worker, where everything actually happens

Everything you see at https://lmjtfy.fun comes out of this folder. It is one Cloudflare Worker: a small program Cloudflare runs at its edge, started fresh for each request in milliseconds, written here in Rust and compiled to WebAssembly. There is no server to keep running. A request arrives, the Worker answers it, and that is that.

Two things have to outlive a single request: what was already asked (so it is never asked again) and how much of today's budget is left. For those the Worker has two Durable Objects, which are the closest Cloudflare comes to a tiny server with a memory: one object per name, anywhere in the world, with its own SQLite. The Worker talks to them; they remember.

flowchart LR
  V["Visitor's browser"] -->|"/, /ask, /gate"| W["Worker (src/lib.rs)"]
  V -->|"/live (socket)"| A
  G["git clone"] -->|"/lmjtfy.git"| W
  W --> A["Archive object: every call, the feed, the sockets"]
  W --> B["Budget object: today's spend, per-visitor limits"]
  A --> J["Jev (TypeSafe)"]
  A --> L["LLM (Workers AI)"]
  W --> GH["GitHub (read-only token)"]

What it serves

AddressWhat happens
/ and /?q=...The page. With ?q=, it types the question in for you and asks.
POST /askAnswers a question as a stream: the whole transcript, re-sent as each call finishes.
POST /rateA browser's 👍 or 👎 on Jev's answer, and the votes after it.
POST /seenA page's report of itself as it is left: how long it was in view, how far down, the screen; or a link followed off the site. Kept, and answered with nothing.
POST /gateThe facts request, run as you type, 300 ms after each pause, so the answer starts from what Jev already said.
/feedThe next page of "asked lately", which the feed asks for when it is scrolled to its end. It reads the archive and asks nobody anything.
/liveA WebSocket each open page holds: how many are online, toasts, live feeds, and "a new version is live".
/rulesThe rules engine with every fact clickable, asking nobody anything.
/rules.svgThe rules drawn as a standalone picture, for the docs (chapter 6).
/card.pngThe picture a link unfurls with (chapter 11).
/icons/<name>.pngThe explorer's file and folder icons (chapter 15⅝).
/lmjtfy.gitgit clone it, or open it in a browser and read the code (you are probably here).
/jevcrates.git, /postjevsql.git, /jevsnes.git, /jevhooks.gitThe client lmjtfy shares with the owner's three other Jev projects, and those projects, served the same way.

Try it. Open https://lmjtfy.fun/rules and click answerable until it says no. Watch every rule but one go grey. That is the engine from chapter 6 running in your browser's address bar.

Live: who is here, and what just happened

Every browser with the site open keeps one socket to /live, held by the archive object. Its sockets are "hibernatable": while nothing happens, the object can sleep and nothing is billed, and the sockets stay open.

One socket per browser, not per tab: the socket belongs to a SharedWorker (src/live.js), a script the browser runs once for all of a site's tabs and keeps while any of them is open. Each tab talks to it, and it passes on everything the archive sends; a tab that opens later is handed the current count and places at once. A tab the browser freezes in the background cannot take the socket down with it, which is what goes wrong when one tab holds the socket for the rest. A browser without SharedWorker opens a socket per tab, as every page used to.

Aside. Pages of different builds use different workers (the build is in the worker's address), so after a deploy a reloaded tab gets a fresh one while a tab from before keeps its own until it reloads too.

It pushes four things:

  • How many browsers are open, and where, shown as "N online" in the top bar; hover it for a flag and a place for each, most first. The place is Cloudflare's: every request arrives with a rough country and city, and the Worker passes them to the archive when a page connects (in headers it sets over any the page sent, so a page cannot name its own). The archive keeps them on that page's socket while it is open, for the list. (What is kept for good is below, in What is kept about visitors.) Pages are told at most once a second, so a deploy, which reconnects every page at once, is one update rather than one per page. A page that reconnects while the deploy is still reaching Cloudflare's edge can come through the Worker from before, which passes no place; ten seconds on, when the deploy has settled, the archive asks it to reconnect (close code 1012), and it comes back placed.
  • A toast when someone asks a question or clones the code. A question the feed may show is shown with Jev's answer; any other is "someone asked Jev something". The page that asked does not get its own toast. The site sends pages nothing else about who is connected: no address, no browser, no history.
  • The feeds themselves. After each toast the archive sends the home page's feeds as HTML elements with ids, and a page replaces the ones it has: most asked and so far whole. A question just asked goes to the top of asked lately, taking its line from wherever it was, so the older lines a page has scrolled in stay put. No refresh.
  • The build. Every page carries the commit its Worker was built from (build.rs). A deploy restarts the archive object, every socket closes, every page reconnects, and the archive tells each one the build that is live now. A page from an older build shows a toast that stays, with a Reload button. If the deployed commit has a Release-Note: trailer, an older page also gets that line as a toast, so the people on the site hear what changed.

The code pages

git clone --recurse-submodules https://lmjtfy.fun/lmjtfy.git

The repository is private on GitHub, and is shared from here instead, so nobody needs a GitHub account and nothing is announced. The Worker speaks git's smart HTTP (src/clone.rs): the two requests a clone or fetch makes, info/refs?service=git-upload-pack and git-upload-pack, are forwarded to GitHub with a fine-grained token that can read deizel/lmjtfy and deizel/jevcrates and nothing else. A push is refused here, and GitHub would refuse it anyway. jevcrates is served beside it at /jevcrates.git, because .gitmodules names it by the relative URL ../jevcrates.git, which git resolves against wherever lmjtfy was cloned from. The owner's three other Jev projects (postjevsql, jevsnes, jevhooks) are served too, and name jevcrates the same way, so each clones with its submodule from here.

Clones and pulls are counted, anonymously, per repository; "So far" shows lmjtfy's, and a clone of any project but jevcrates is toasted. jevcrates is fetched along with every project cloned with its submodules, so its toasts would double up. A pull names the commits it already has (have lines) and a clone does not; a fetch counts on the round GitHub answers with the pack, so a long negotiation counts once and a pull with nothing new is not counted.

Git only ever asks for lmjtfy.git/info/refs and lmjtfy.git/git-upload-pack, so every other path under /lmjtfy.git is free for people. The code pages (src/browse.rs) are laid out like an editor, full width. On the left, a sidebar of panels: the explorer (the whole tree, jevcrates included, opened on the way to the page you are on), the outline of the page's headings, the clone command, the latest commit, and every repository served here; jevcrates' pages add the Cargo.toml lines for depending on it, pinned to its latest commit. On the right, the page: a folder's README and CLAUDE.md as two tabs, "for people" and "for agents", between a ◀ tab for the chapter before and a ▶ tab for the chapter after (from the chapter's own last line, or, in a repository with no guide, the next folder with a README in a depth-first walk), with their diagrams drawn (click one to enlarge it); a source file with its comments rendered on the left and the code they are about on the right, coloured (chapter 12½), or rendered if it is markdown, each with a tab for its source top to bottom; ?raw gives a file as it is. /lmjtfy.git itself is the root folder, whose README is the prologue. On a phone the sidebar moves below the page.

Everything is read from GitHub's API with the same token, kept a minute per isolate: folders and files from the contents API, the explorer's tree in one request from the git trees API. jevcrates is browsed under third-party/jevcrates/ at the commit lmjtfy pins.

Storage

Two Durable Objects keep everything that outlasts a request. Each is one object for the whole site (id_from_name), so every visitor reads the same rows.

The archive (src/archive.rs)

A SQLite database, changed only by adding a step to MIGRATIONS. The tables share no keys, events.browser aside: an asked row's answers are what the versions behind it said, copied, and counts is a tally.

askers is how a question counts each browser once. A browser's first page gives it a random id in a cookie (lmjtfy_browser, a year); when it asks, the Worker hashes the id with the question and the archive keeps only that. A browser asking again, or opening its own question from the feed, finds its row and is not counted, toasted or moved up the feed again. The rows of two questions from one browser have nothing in common, and the id cannot be had back from one. So the key says nothing about who asked; the browser column beside it, which the owner's backend reads, is the id itself. An ask with no cookie (a script) counts every time, as before. Counts from before 2026-10-02 stay as they were.

ratings holds the 👍 and 👎 under each answer ("Was Jev right?"): one vote per browser, keyed the same way, and pressed again to take it back. A vote is on the answer as it was kept when it was cast, by the hash of its answers, so if the question is answered differently later, that answer starts with no votes and the old one keeps its own.

erDiagram
  versions {
    text sent_to PK "Jev's endpoint, or a Workers AI model id"
    text request PK "the exact body sent"
    integer version PK "1, 2, ...: each time it was sent, newest last"
    text response "the exact body that came back"
    text request_id "Jev's id for the call, if it gave one"
    integer attempts "tries the client made"
    real took_ms
    real answered_ms "when it was answered"
  }
  asked {
    text input PK "the question, cleaned"
    text answers "JSON: one Answer per question Jev answered"
    real asked_ms "last asked"
    integer times "how often it was asked"
    integer listed "1 if the feed may show it (Jev's fit fact)"
    integer llm "1 if an LLM had to be asked"
    integer moderated "the owner's say: 1 show, 0 hide, NULL leave it to listed"
  }
  events {
    integer id PK
    real at_ms "when"
    text what "view, answer, gate, vote, more, card, fetch, moved, live, left, read, out"
    text method
    text host
    text path
    text query "as it came"
    text input "the question typed, asked or voted on"
    text detail "how an ask ended, which way a vote went, clone or pull"
    real status
    real sent "calls sent for it"
    real kept "calls answered from versions"
    real llm
    real took_ms "an answer's time, or how long a page stayed"
    real first "1 on a browser's first page ever"
    real daily "1 on its first page of the UTC day"
    real session "1 on its first page in half an hour"
    text referrer "the page that linked here, whole"
    text source "utm_source or ref"
    text client "browser, git, bot, other"
    text family "Chrome, Firefox, ..."
    text os
    text device "mobile or desktop"
    text language "Accept-Language"
    text agent "User-Agent"
    text browser "the lmjtfy_browser cookie"
    text ip
    text country
    text region
    text city
    text postcode
    text timezone
    real latitude
    real longitude
    real asn "the network's number"
    text network "whose network: the ISP"
    text colo "Cloudflare's data centre"
    text protocol "HTTP and TLS versions"
    text medium "utm_medium"
    text campaign "utm_campaign"
    text screen "1920x1080, as the page says"
    text viewport "the window"
    real scroll "how far down the page was seen, 0 to 1"
  }
  counts {
    text name PK "sent, kept, clone:lmjtfy.git, pull:lmjtfy.git, ..."
    integer n
  }
  askers {
    text input PK "the question"
    text who PK "SHA-256 of a browser's id and the question"
    text browser "the browser's id itself, for the owner's reading"
  }
  ratings {
    text input PK "the question"
    text answer PK "SHA-256 of the answers as kept"
    text who PK "as in askers"
    integer vote "1 right, -1 wrong"
    text browser "as in askers"
  }
  migrated {
    integer version PK "MIGRATIONS steps run"
  }

It also holds, in memory only, the calls on the wire (so identical asks wait on one) and the /live sockets of every open page.

The budgets (src/meter.rs)

Key-value storage, a JSON value per key.

erDiagram
  neurons {
    integer day "days since 1970, UTC"
    real used "Workers AI neurons spent that day"
  }
  jev_dollars {
    integer day
    real used "dollars of Jev spent that day"
  }
  account {
    integer day
    real at_ms "when Cloudflare's figure was read"
    real neurons "the whole account's neurons that day"
  }

Each visitor's count for the minute (budget::Visits) is in memory and written nowhere by the site.

What is kept about visitors

Everything a request says, for the owner alone. Until 2026-10-03 the site kept nothing about who asked; the owner then ruled the other way ("anywhere in the app where we are dropping data we should plug"), so this is the true account now.

Every request worth keeping is a row of events in the archive (packages/archive/src/event.rs): a page viewed, an ask and how it ended, each as-you-type request, a vote, a clone, a redirect from an old address, a page connecting and leaving, and what each page reports of itself as it is left (how long it was in view, how far down it was read, the screen) or when a link off the site is followed. A row has the question, the visitor's address, their browser's id (the lmjtfy_browser cookie, which ties one browser's rows together), the user agent, the referring page, and what Cloudflare says of where the request came from: the network (the ISP), country, region, city, postcode, timezone and coordinates. The Worker writes the row after the response has gone (wait_until), so keeping it delays nobody.

None of it is shown on the site. A question Jev judged unfit for the feed was always kept (asked.listed); now so is every question that ended any other way, in events.

Visits are counted from a second cookie, lmjtfy_visit, which holds the time the browser was last counted: a page view compares it with now and marks itself the browser's first ever, first today, or first in half an hour.

The admin door

The owner reads all of this from a separate, private Worker, which binds this Worker's archive object (script_name in its wrangler.toml) and posts archive::Admin messages to the object's /admin path:

  • Select: one SQL statement that only reads (archive::reads_only), and its rows back.
  • Moderate: the owner's say on whether a question is on the feed, kept in asked.moderated beside Jev's own verdict in listed. The feed shows COALESCE(moderated, listed).

This Worker has no route that reaches that path. A visitor's request is passed to the object only as a socket upgrade on /live.

What Cloudflare keeps

Cloudflare, which runs the site, keeps its own record, in the owner's account and nowhere public:

  • Workers Logs, for 3 days: one entry per request the Worker or its objects handle, with the request as it arrived, address included.
  • Traces, for 7 days: each request's timing, through the fetches and the Durable Object calls it made.
  • Metrics, the request counts and errors every Worker has.
  • Web Analytics: on lmjtfy.fun, Cloudflare adds its beacon script (static.cloudflareinsights.com/beacon.min.js) to each page it serves to a browser, and the browser reports page views and load times to lmjtfy.fun/cdn-cgi/rum. It sets no cookie. The Worker's own HTML does not contain the script: Cloudflare adds it on the way out, and only for browsers, so curl does not see it.

The owner turned on the logs and traces (2026-10-02) and Web Analytics with the domain (2026-10-03). They are configured in wrangler.toml under [observability].

Credentials

All from 1Password, none ever written to a file here. TypeSafe's API refuses browser origins anyway, so every Jev call goes through the Worker and the key never reaches a page.

  • LMJTFY_TYPESAFE_API_KEY, lmjtfy's own Jev key, is a Worker secret. Under wrangler dev it comes from the process environment, which op-env-run fills from the host's 1Password Environment. Deployed, it is set with lmjtfy-secret. Without it the Worker still runs, and the page says Jev is offline.
  • The Cloudflare API token. Workers AI has no local emulation, so even wrangler dev calls the real models on the real account. lmjtfy-wrangler (from the owner's devshell) reads the token from 1Password per run and execs wrangler with it.
  • LMJTFY_GITHUB_TOKEN, a Worker secret: the clone proxy's GitHub fine-grained token, Contents: read on deizel/lmjtfy and deizel/jevcrates and no other permission. GitHub's API cannot mint a fine-grained token, so it is made on GitHub and kept in the host's 1Password Environment, which op-env-run hands to wrangler dev; deployed, it is set with op-env-run -- lmjtfy-secret github. Without it a clone is answered with 503.
  • CLOUDFLARE_ANALYTICS_TOKEN and CLOUDFLARE_ACCOUNT_ID, Worker secrets, are how the budget object reads the account's usage. The token can read analytics and nothing else; nixos-config's infra declares it (cloudflare_account_token.lmjtfy-analytics). The dev server runs without them and counts only itself.

In this folder

PathWhat
src/The Worker's code. Chapter 3 walks through it.
wrangler.tomlThe Worker's name, the pinned Jev model, the chosen LLM, the Jev budget, the bindings and the declared secrets.
build.rsStamps the build with the commit it came from (LMJTFY_BUILD).
Cargo.tomlThe crate: a cdylib for the Worker, and an rlib so its tests run natively.

build/ and .wrangler/ are the build's and the dev server's and are not committed.

← Previous: Chapter 1, apps/ · Up: apps · Next: Chapter 3, the source →