The Decision That Shapes Everything Downstream
An aggregator has to answer one question before any others: how does the outside world get in? There are two families of answer. In the first, coverage lives in code — a module per publisher, a parser per site, a deploy every time you want a new one. In the second, coverage lives in data: a table of source rows, each row naming a kind and a configuration, and a small number of handlers that know how to execute each kind.
The second is not merely tidier. It changes who is allowed to extend the system. When adding a section to your morning reading is a row insert, the person doing it is the person reading; when it is a code change, the person doing it is whoever can deploy. For a tool with an audience of two, that difference is the whole product.
What a Source Row Actually Holds
The interesting columns are not the obvious ones. A row carries its kind — a syndicated outlet feed, a search-engine news query, a provider API — and a configuration blob whose meaning depends on that kind. Then it carries the state the fetch loop needs to be polite: the validators the server last handed back, when it was last fetched, how many times in a row it has failed, whether it is currently enabled, and if not, why.
That last group is the part people leave out of the first version and regret. Politeness is not a behavior you remember to perform; it is state you keep, per source, so the next round can behave correctly without recomputing anything.
No Shelf Fetches While It Renders
The companion rule is narrower than the slogan people usually reach for, and the narrowness is the interesting part. Shelf rendering never reaches the network: a tab is served entirely from the store, and ingest happens on a schedule or when someone presses refresh. What the rule does not say is that no handler ever fetches — opening an article does, on its first open, because extraction is deliberately lazy. That is one bounded request caused by one deliberate tap, which is a different thing from a list view quietly making forty.
This is what keeps the reader honest under load. A shelf that fetches while rendering has a latency that depends on somebody else's server, fails in ways that look like your bug, and — because renders are frequent and unpredictable — turns your polite client into an accidental hammer. Reading from the store and refreshing on a schedule decouples the two completely, and it means the answer to "why is this slow?" is always somewhere in your own process.
The Bit That Is Not Free
Declared sources buy provenance and editability. They do not buy identity. A source row tells you where an article entered from; it says nothing about whether the story behind it already entered from somewhere else. Those are separate problems and they need separate mechanisms, and conflating them is how a reader ends up showing the same event seven times while its architecture document claims it deduplicates.