[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-11395":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":14,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":16,"starSnapshotCount":16,"syncStatus":17,"lastSyncTime":29,"discoverSource":30},11395,"halupedia","BaderBC\u002Fhalupedia","BaderBC","Encyclopedia that hallucinates articles on the fly","https:\u002F\u002Fhalupedia.com",null,"TypeScript",219,25,4,15,0,2,172,6,60.24,"GNU General Public License v3.0",false,"master",true,[],"2026-06-12 04:00:55","# Halupedia\n\n> *\"Comprehensive coverage of topics mainstream encyclopedias overlooked.\"*\n\nAn infinite, hallucinated encyclopedia. Every link leads to an entry that does\nnot exist yet — until you click it, at which point an LLM pretends it has\nalways existed and writes it for you, in the deadpan register of a 19th-century\nscholarly press.\n\nLive at **[halupedia.com](https:\u002F\u002Fhalupedia.com)**.\nCooked on a Cloudflare Worker. Cached forever in KV. Threaded HN-style comments\nunder every article, no signup, AI-hallucinated identities. Patrons may\n[buy us tokens](https:\u002F\u002Fbuymeacoffee.com\u002Fbaderbc) so the press can keep\nprinting. Editors and conspirators meet in the\n[Discord](https:\u002F\u002Fdiscord.gg\u002FfKMnyNwtGc).\n\n---\n\n## Table of contents\n\n- [What it is](#what-it-is)\n- [Star history](#star-history)\n- [How a page is born](#how-a-page-is-born)\n- [Consistency in a hallucinated universe](#consistency-in-a-hallucinated-universe)\n- [Comments](#comments)\n- [Defenses against runaway costs](#defenses-against-runaway-costs)\n- [Architecture](#architecture)\n- [Local development](#local-development)\n- [Deploying your own instance](#deploying-your-own-instance)\n- [Configuration](#configuration)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## What it is\n\nHalupedia is a single-page Cloudflare Worker that:\n\n1. Serves a React SPA that looks like an old print encyclopedia.\n2. On a request for any unknown slug, calls an LLM (via OpenRouter) that\n   returns an HTML article in the encyclopedia's voice — full of confident,\n   plausible-sounding nonsense that is densely cross-linked to other entries\n   that also do not yet exist.\n3. Caches that article in Cloudflare KV forever. Subsequent visits are free.\n4. Lets readers leave HN-style threaded comments without ever signing up.\n   Names are hallucinated by the LLM on first comment and tied to a cookie.\n\nThere is no editorial staff, no truth, no warranty. Every article is invented\non demand. The footnotes are also lies.\n\n## Star history\n\n\u003Ca href=\"https:\u002F\u002Fwww.star-history.com\u002F?repos=BaderBC%2Fhalupedia&type=date&legend=top-left\">\n \u003Cpicture>\n   \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=BaderBC\u002Fhalupedia&type=date&theme=dark&legend=top-left\" \u002F>\n   \u003Csource media=\"(prefers-color-scheme: light)\" srcset=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=BaderBC\u002Fhalupedia&type=date&legend=top-left\" \u002F>\n   \u003Cimg alt=\"Star History Chart\" src=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=BaderBC\u002Fhalupedia&type=date&legend=top-left\" \u002F>\n \u003C\u002Fpicture>\n\u003C\u002Fa>\n\n## How a page is born\n\n```\n   you click  ─►  \u002Fapi\u002Fpage\u002Ffootnote-drift\n                       │\n                       ▼\n              ┌──────────────────┐\n              │ in KV already?   │── yes ─► stream from KV (free)\n              └──────────────────┘\n                       │ no\n                       ▼\n              ┌──────────────────┐\n              │ are you a bot?   │── yes ─► 404 (no token spend)\n              └──────────────────┘\n                       │ no\n                       ▼\n              ┌──────────────────┐\n              │ over IP rate?    │── yes ─► 429 with Retry-After\n              └──────────────────┘\n                       │ no\n                       ▼\n              ┌──────────────────────────────────┐\n              │ load prior link-hints from D1    │\n              │ (canon set by other articles)    │\n              └──────────────────────────────────┘\n                       │\n                       ▼\n              ┌──────────────────────────────────┐\n              │ stream LLM via OpenRouter        │\n              │  → split stream:                 │\n              │    a) sanitize + send to client  │\n              │    b) collect, persist to KV,    │\n              │       extract & save link hints  │\n              └──────────────────────────────────┘\n```\n\nThe HTML stream is split (`ReadableStream.tee()`) so the user starts reading\nthe article *while* the worker is still receiving and persisting it. First\npaint is sub-second; the worker continues writing to KV under\n`ctx.waitUntil()` after the response closes.\n\n## Consistency in a hallucinated universe\n\nThe hardest problem with an infinite, on-demand encyclopedia is internal\ncontradiction: article A says Mortimer Vellum died in 1843; article B,\ngenerated three weeks later, says he was alive in 1881. Halupedia solves\nthis with **link hints**:\n\n- When the LLM writes an article, it is required to add a `context=\"…\"`\n  attribute on every `\u003Ca>` it inserts, summarising the *future* article it is\n  linking to (e.g. `context=\"19th-century clerk who formalized footnote drift,\n  Pellbrick's mentor\"`).\n- Before serving the HTML, the worker harvests these `context` values into a\n  `link_hints` table in D1, keyed by `(target_slug, source_slug)`.\n- The `context` attribute is stripped before the HTML is sent to the browser —\n  readers never see the metadata.\n- When that target article is later requested for the first time, the worker\n  loads the accumulated hints and injects them into the system prompt as\n  **\"PRIOR REFERENCES — these are CANON\"**. The LLM is instructed that the\n  encyclopedia is hallucinated and absurd, but it must not contradict itself.\n\nThe result is a write-forward consistency mechanism: each article seeds\nbreadcrumbs for the entries it links to, so by the time those entries are\nwritten, the LLM has a small dossier of established lore to honour.\n\n## Comments\n\nHacker-News-style threaded comments under every article. Backed by Cloudflare\nD1 (free tier). Notable behaviours:\n\n- **No signup, ever.** The first time you post, the LLM hallucinates a `name`\n  and `username` for you (e.g. *Bartram Pellbrick-Thwaite* \u002F\n  `pellbrick_archivist`), in the same scholarly register as the rest of the\n  site. You are inserted into D1 with a UUID and given a `hu_uid` cookie.\n- **Cookie is effectively permanent** (capped at 400 days per RFC 6265bis,\n  refreshed on every authenticated request — so active users never expire).\n- **One upvote per comment per user**, toggleable. Optimistic UI.\n- **Threaded** to arbitrary depth, sorted by `score DESC, created_at ASC`.\n- **Author auto-upvotes their own post**, so every comment opens at score 1.\n- **Per-IP rate limit on identity creation** so a botnet can't burn your\n  budget by minting fresh hallucinated names in a loop.\n\n## Defenses against runaway costs\n\nLLM tokens cost real money and Halupedia is run by one person who lacks\na corporate Amex. The worker has a layered defense:\n\n| Layer | Catches | Implementation |\n|---|---|---|\n| 1. User-Agent regex | Honest crawlers (Googlebot, GPTBot, ClaudeBot, curl, wget, scrapy, …) | `src\u002Fworker\u002Findex.ts` `isBot()` |\n| 2. Per-IP article gen budget | UA-forging scrapers, runaway tabs | KV-backed fixed-window limiter, `GEN_PER_IP_PER_HOUR` |\n| 3. Per-IP identity-mint budget | Cookie-rotating spammers minting hallucinated names | `IDENT_PER_IP_PER_HOUR` |\n| 4. Global daily cap | Distributed botnets that defeat 1–3 | `MAX_ARTICLES_PER_DAY`, KV counter |\n| 5. Cache forever | Same slug never costs twice | KV `put()` with `metadata` |\n| 6. Tee-and-persist | Stream interruptions don't waste a generation | `ReadableStream.tee()` + `waitUntil()` |\n| 7. Cloudflare dashboard | Volumetric \u002F L7 attacks | WAF rate-limit + Bot Fight Mode |\n\nCrucially: **cached articles are served to everyone, including bots.** The\nbot guard only fires on uncached slugs, so anything you've already paid to\ngenerate stays freely indexable for SEO.\n\n## Architecture\n\n```\nsrc\u002F\n├── worker\u002F\n│   ├── index.ts        ← Hono app, request routing, generation pipeline\n│   ├── llm.ts          ← OpenRouter streaming client + system prompt\n│   ├── sanitize.ts     ← HTML allowlist + extracts link-hint metadata\n│   ├── hints.ts        ← D1 read\u002Fwrite for cross-article canon\n│   ├── identity.ts     ← LLM call that hallucinates {name, username}\n│   ├── comments.ts     ← Hono sub-app: threaded comments + voting + cookies\n│   ├── ratelimit.ts    ← Per-IP fixed-window KV limiter\n│   ├── slug.ts         ← Slug normalisation + reserved-slug list\n│   ├── seed.ts         ← Curated seed entries for the homepage\n│   └── env.d.ts        ← Worker env type\n├── client\u002F\n│   ├── App.tsx         ← SPA shell, history routing, streaming reader\n│   ├── Comments.tsx    ← Threaded HN-style comment UI\n│   ├── AllEntries.tsx  ← A–Z register of every article ever cached\n│   └── styles.css      ← Single hand-rolled stylesheet (parchment aesthetic)\n├── shared\u002F\n│   └── …               ← Types shared between worker & client\n└── ...\n\nmigrations\u002F\n├── 0001_init.sql       ← users, comments, votes\n└── 0002_link_hints.sql ← (target_slug, source_slug) → blurb\n```\n\nStack:\n\n- **Cloudflare Workers** — execution, runs everywhere, free tier covers viral.\n- **Cloudflare KV** — article HTML cache, stores `{title, generatedAt}` in metadata.\n- **Cloudflare D1** — comments, users, votes, link hints.\n- **Hono** — small router + cookie helpers.\n- **OpenRouter** — LLM access (model is configurable via env var).\n- **Vite + React 18** — SPA, no router library; history API by hand.\n- **No build step on the worker.** Wrangler bundles `src\u002Fworker\u002Findex.ts` and\n  serves the Vite output as static assets via the `ASSETS` binding.\n\n## Local development\n\nYou will need: Node 20+, pnpm 9, and a Cloudflare account.\n\n```bash\npnpm install\n\n# Create a D1 database (one-time)\npnpm wrangler d1 create hallupedia\n# Copy the printed database_id into wrangler.toml, replacing the placeholder.\n\n# Apply migrations locally\npnpm wrangler d1 migrations apply hallupedia --local\n\n# Run vite (client) + wrangler (worker) concurrently\npnpm dev\n```\n\nOpen \u003Chttp:\u002F\u002Flocalhost:8787>. Articles will be generated on demand if you set\nyour `OPENROUTER_API_KEY` (see below); otherwise the homepage seed will\ndisplay but new entries will fail.\n\nYou can hit `http:\u002F\u002Flocalhost:8787\u002Fapi\u002Findex?refresh=1` at any time to force\nthe total-entries counter to recount the KV namespace.\n\n## Deploying your own instance\n\n```bash\n# 1. Configure secrets\npnpm wrangler secret put OPENROUTER_API_KEY\n\n# 2. Apply migrations to the remote D1\npnpm wrangler d1 migrations apply hallupedia --remote\n\n# 3. Deploy\npnpm run deploy\n```\n\nThe Worker handles its own routing, including `\u002Frobots.txt`, the SPA shell,\nand the API. If you bind a custom domain, edit the `[[routes]]` block in\n`wrangler.toml`. To deploy to a `*.workers.dev` URL instead, set\n`workers_dev = true` and remove the routes.\n\n## Configuration\n\nDefined in `wrangler.toml` under `[vars]`:\n\n| Var | Default | Purpose |\n|---|---|---|\n| `OPENROUTER_MODEL` | (set in toml) | Model slug used for both article gen and identity hallucination |\n| `MAX_ARTICLES_PER_DAY` | `5000` | Global circuit breaker — soft cap per UTC day |\n| `GEN_PER_IP_PER_HOUR` | `100` | Per-IP article generation budget |\n| `IDENT_PER_IP_PER_HOUR` | `10` | Per-IP cap on minting new commenter identities |\n\nSecrets (set via `wrangler secret put`):\n\n| Secret | Purpose |\n|---|---|\n| `OPENROUTER_API_KEY` | Auth for OpenRouter |\n\nBindings:\n\n- `ARTICLES` — KV namespace for article HTML.\n- `DB` — D1 database for comments + link hints.\n- `ASSETS` — static assets (Vite build output).\n\n## Contributing\n\nPull requests welcome, especially anything that:\n\n- Reduces token spend per article without making the prose worse.\n- Improves cross-article consistency further.\n- Hardens the bot\u002FUA defenses without breaking real readers.\n- Catches a \"griffing\" \u002F prompt-injection vector you found in the wild.\n\nPlease open an issue first for anything user-facing so we can discuss tone —\nHalupedia lives or dies by its voice and an out-of-register entry is\nworse than no entry at all.\n\n## License\n\nGPL-3.0. The source code in this repository is free software: you can\nredistribute it and\u002For modify it under the terms of the GNU General Public\nLicense as published by the Free Software Foundation, either version 3 of the\nLicense, or (at your option) any later version.\n\nIf the press has improved your day, you may\n[buy us tokens](https:\u002F\u002Fbuymeacoffee.com\u002Fbaderbc) or join the conversation on\n[Discord](https:\u002F\u002Fdiscord.gg\u002FfKMnyNwtGc).\n","Halupedia 是一个即时生成文章的在线百科全书。它通过调用大型语言模型（LLM）来动态创建不存在的文章，并以19世纪学术出版物的风格呈现，确保每篇文章都充满自信且看似合理的虚构内容。该项目使用TypeScript编写，部署在Cloudflare Worker上，并利用Cloudflare KV进行永久缓存。用户可以无需注册即发表HN风格的评论，评论者的名字也是由AI生成并绑定到cookie。Halupedia适合那些希望探索非传统知识领域或享受随机生成内容乐趣的用户。","2026-06-11 03:31:46","CREATED_QUERY"]