How this site is built
WordPress collects, a Worker clusters, HTML comes out fast.
- 1. CollectWP RSS Aggregator imports each outlet's feed into WordPress.
- 2. ExposeA read-only REST endpoint lists the imported items.
- 3. ClusterA Cloudflare Worker embeds headlines and groups duplicates.
- 4. RenderThe same Worker serves this page as plain HTML.
WordPress and WP RSS Aggregator
The back end is a WordPress site running WP RSS Aggregator. It fetches the feeds of the crypto outlets on a schedule and stores every entry as an item with its title, excerpt, publish time, original URL and source name. WordPress doesn't render anything you see here.
A read-only endpoint
A small must-use plugin adds one GET route, /wp-json/ch/v1/items, that returns those items in pages. It has no write path.
The Worker
A Cloudflare Worker (TypeScript, Hono) runs a cron every five minutes. Each run pulls the new items from the endpoint and stores the last 14 days in a D1 database.
Clustering with embeddings
Every item is turned into a vector by the Workers AI model @cf/baai/bge-base-en-v1.5, using the title plus the first sentence of the excerpt. A new item is compared by cosine similarity with items from stories active in the last 48 hours. It joins the closest story when the similarity is at least 0.84 and the two share one distinctive token such as a company, ticker or amount, or at least 0.80 with two shared tokens. Two items from the same outlet need 0.92, so one outlet's follow-ups don't inflate the count. Generic words like bitcoin, ETF or SEC never count as a shared token, and the item must also resemble the story's other items on average so a story can't drift into a neighbouring one.
The result is one story with several outlets behind it. The count next to each headline is the number of distinct outlets in that group.
Ranking and topics
A story's score is its outlet count divided by (hours since it was last updated + 2) raised to the power 1.5. More outlets push a story up, age pulls it down. Topics come from keyword rules on the headline and excerpt, and a story keeps a topic when its lead headline has it or at least half of its items do.
Serving
Pages are rendered on the server from D1 with plain HTML and inline CSS. There's no front-end framework and no client-side JavaScript. Headlines link out to the original outlets with nofollow. Story pages are marked noindex, because they only repackage other outlets' headlines. The same data is available as JSON at /api/stories.