This post is being written on the thing it describes. A few days ago, every RSC instance you're reading this from swapped its entire data model โ the part that decides what a post is, who wrote it, and how conversations hang together โ while staying online. Here's the story, with the numbers.
The old model did what most feed readers do: fetch a feed, keep the latest copy of each item, overwrite on change. Simple, and quietly lossy.
The new model โ we call the read side of it the projector โ works like a courtroom instead:
Never store conclusions. Store evidence โ every delivery of every item from every source, every version observed, every attribution claim with its strength โ and derive what the reader sees, fresh, at read time.
That one idea is why moderation is instant and reversible, why an author's name comes from their attribution instead of whatever arrived last, and why a blocked source's posts vanish everywhere at the next read without deleting a byte of evidence.
| verticals shipped | 4 (control plane, logical items, moderation + verification, migration) |
| commits on the branch | ~140 |
| tests at cutover | 1,084 core + 330 web |
| defects caught by review before deploy | ~30, including 5 that only a whole-system pass could |
| defects found by dogfooding, fixed same day | 12 |
The dogfooding finds were the humbling part. My own blog showed up as a publisher named "Interesting read as always !" โ the pipeline had promoted an item title to an author name. Replies arrived before their parents and stayed orphaned forever because a scheduler was built, tested, and never wired in. Podcasts carried audio in the data model and the UI just... never rendered it. Every one of those is now a regression test.
The flip itself is one atomic transaction at startup: convert every legacy user, post, follow, and push lease into evidence โ same IDs, same permalinks, byte-exact WebSub leases โ write a marker, open for traffic.
If it crashes halfway, nothing changed and it retries next boot.
It worked. And then the timeline took six seconds to load. ๐
SQLite never indexes foreign keys for you, and the projector looks things up per item. On a table of 32,000 identity keys, every lookup was a full scan:
EXPLAIN QUERY PLAN
SELECT key FROM logical_identity_keys_v2 WHERE logical_item_id = ?;
-- before: SCAN logical_identity_keys_v2 (478ms for a 50-item page)
-- after: SEARCH ... USING INDEX (1ms)
Two migrations later every foreign key in the model is indexed, a CI guardrail reflectively walks the schema and fails the build if any future table ships an unindexed key, and the timeline renders in ~40ms โ faster than v1 ever was.
source:inReplyTo now travels in every feed we publishThe old model is still in the codebase, dark, waiting for its retirement release once this soak period ends. The feeds never stopped.
That was the whole point: conversations travel as RSS, and the plumbing underneath them should be replaceable without anyone's reader noticing.
Built in the open โ the repo, the specs, and every review document live at github.com/rmdes/textcaster.