
The Order of Discovery
The Citizen's Daily Brief keeps two clocks — a timeline of when things happened, and a source list of when news outlets filed. The spec wanted one component doing both jobs. Splitting them was the better call.
Open any item in the Citizen’s Daily Brief and there’s a short chronology hanging off a thin vertical rule directly under the opening paragraph. Usually three or four entries. Grey dates in sans-serif, descriptions in the same Georgia as the body text, no dots, no icons, no color. Nothing to click. It’s just there, the way a footnote is there.
That component was supposed to be something else.
What the Spec Asked For
The original spec of the app placed the timeline inside the expandable evidence panel, item five of six, between the common-ground checklist and the source list. It sketched the component like this:
Mar 9, 6:00pm o Federal Reserve releases policy statement
Mar 9, 6:03pm o AP reports rate decision
Mar 9, 6:15pm o Bloomberg analysis published
Mar 9, 6:30pm o Markets adjust expectations
Minute precision, a type indicator on every row, and a claim in the spec’s own commentary that this was “genuinely novel — no other news product shows readers how a story developed, which sources were first, and whether official action preceded or followed reporting.”
Look at those four rows again. Two are events: the Fed releases a statement, markets move. Two are publications: AP files, Bloomberg publishes. The mock is quietly doing two different jobs in one column, and I didn’t notice until I tried to generate the data for it.
Two Kinds of Clock
Most news timelines answer when did this happen. A bill was introduced Monday, the committee voted Wednesday, the chamber voted Friday. That’s a chronology of events, and it’s the one every news product uses.
The other chronology answers when did we find out. A wire service filed at 7:41 AM. A national broadcaster picked it up at 4:58 PM. A government statement landed the next morning. Same story, different question.
Philosophically the event exists whether or not anyone reports it, and happened when it happened. Practically, for a reader trying to form an accurate picture, the reporting is the event. The story that breaks at 2 PM on a wire is a different epistemic object than the same story once a specialist publication has contextualized it twelve hours later. The facts can be identical while your understanding of them is not.
Intelligence analysts treat this as data in its own right. A story that surfaces informally, then gets wire confirmation, then gets official acknowledgment has a different information signature than one that opens as an official announcement and gets explained backward by reporting. The first pattern suggests something leaked. The second suggests something was managed. Neither tells you which story matters more, and both tell you something about the environment the story came out of.
What Got Built Instead
So in the CDB, the two kinds of timing ship as two separate components.
The timeline under the lede is the event clock. It runs at day granularity with a rough time-of-day qualifier, and it exists to give you the shape of the story before you read the analysis, the beats as they unfolded over time.

The discovery clock lives one layer down, in the Articles section of the evidence panel. Every source gets its outlet, its role in the assessment, its headline, and its publication time to the minute in Eastern time.

The two corroborate each other, which is the useful part. Take the July 27 wildfire item. Its first timeline entry is dated Jul 26 (early) and reads, in part, Reuters and FT first report Bordeaux threat. That’s a checkable claim. Scroll the source list past the international and national outlets to the wire block and there they are: Financial Times filed at 7:41 AM, Reuters at 8:37 AM, both on the 26th, both earlier than anything else in the cluster of sixteen. The assessment says an outlet was first. The source list hands you the timestamp to test it with.
Which is all the wildfire item’s timing proves. It’s the ordinary case, and the ordinary case has no tell in it. A fire is a fire, the wires get there first, everyone else catches up across eighteen hours, and the sequence tells you nothing you didn’t already assume. That’s the right answer for most stories, and most stories are most of the brief.
When the Sequence Is the Story
The archive holds the other kind too. On June 30 the Supreme Court upheld state bans on transgender athletes in school sports. The first source in that item is a report of the ruling at 10:04 AM, classed primary because it broke the news of an official action. The second is at 10:06. Then 10:15, 10:17, 10:43, 11:05, 11:18 — twenty-eight sources, and everything past the first two minutes is commentary on a document that was already public. Official action, then a wave of reaction that couldn’t have started earlier because until 10:04 there was nothing to react to. A scheduled release looks exactly like that.
Six weeks earlier the shape is inverted. On May 15 the brief carried “US Justice Department preparing to indict former Cuban President Raúl Castro.” The Guardian filed at 1:31 PM. The New York Post at 3:47. Reuters at 4:35 and again at 5:17, AP at 5:50, ABC at 6:08. Nothing in the cluster gets classed as a primary source until 6:33 PM, five hours behind the first report. A story about a forthcoming indictment circulated most of an afternoon before anything official caught up with it. The word preparing in that headline is doing a lot of work, and the timestamps are where you can watch it work.
I’d put a caveat on the second one. “Primary” is the assessor’s judgment about what a source is doing in the cluster, not a certificate that the source is an official document, so a five-hour gap is evidence rather than proof. But it’s evidence the reader can see and weigh, which is the entire point of putting the timestamps there.
Splitting the two clocks was an accident of implementation that turned out to be a better design than the spec. The event chronology belongs above the fold because it’s orientation, and every reader needs it. The discovery chronology belongs behind a disclosure because it’s an audit trail, and an audit trail presented as narrative starts to look like the story rather than the receipt for it.
The Field That Never Shipped
The original schema for a timeline entry was {event_time, event_type, label}. That event_type was the hook for a categorization pass I’d planned: color-coded dots, blue for wire services, green for government sources, orange for international outlets. Informative at a glance.
It was also subtly dishonest. Color coding implies categories that map onto significance, and these don’t. A government source isn’t inherently more credible than a wire service. An international outlet isn’t inherently more or less important than a domestic one. The scheme would have encoded editorial judgments I had no business making and no way to defend. Worse, for this project: style for style’s sake.
So the dots never got built, and the field never got populated. What the pipeline emits is {date, event}. I checked: 2,912 timeline entries across 925 published items, every single one that shape. The frontend still carries a fallback branch that reads event_time and label if date and event are missing. It has never fired. Somewhere in briefUtils.ts there’s a four-line return for a schema that lost an argument and has been dead code ever since.
The monochrome version that shipped in its place makes you read the entries instead of scanning the colors. Each one gives you the date and what happened, and where the reporting is the point, it says so in plain text: Reuters and FT first report Bordeaux threat. You form your own view of the source landscape. This is the same instinct that runs through the rest of the product’s deliberately plain design — present the information, don’t nudge the judgment.
The Bug the Timeline Caused
Here’s the part I didn’t anticipate. The timeline is generated in the same JSON payload as the prose, but for a while it wasn’t shown to any of the validation passes that check the prose.
On June 25 the brief carried an item on the New York mayoral primary that called its subject “Zohran Mamdani” in the timeline and “Zoe Mamdani,” with female pronouns, in the body. Both came out of the same model call. The grounded self-review that should have caught it never saw the timeline field, so it checked a body that was internally consistent and passed it.
That item is the opening example in When the LLM Isn’t the Product, which takes up the general problem: a model writing a confident falsehood inches from a structured field that had the name right the whole time. The part that belongs here is narrower and more embarrassing. The field with the name right in it was the timeline, and nothing was reading it.
That produced two fixes. The fact-check prompt now renders the timeline as plain lines and reads it alongside the prose. And there’s a deterministic check, name_consistency.py, that walks every field in an item, including nested structures, and flags any surname appearing with two different given names. No model call, no name catalog, just a regex and a set. Heuristic and admittedly crude — two genuinely different people sharing a surname in one item will trip it — so a conflict triggers regeneration rather than a silent edit.
Structured output that no reviewer reads is structured output you’re publishing unverified. The timeline was the field that taught me that, which is a mildly humiliating thing to learn from a list of dates.
What I Haven’t Built
The pipeline doesn’t detect patterns in the discovery record. If three outlets file the same fact within four minutes of each other, that’s a coordinated release or a shared wire origin, and it’s a materially different signal from three outlets confirming independently across six hours. The data supports the inference. Nothing in the code draws it. That felt like over-engineering for v1 and it still does, though it’s the first thing I’d look into adding if the panel ever expands.
The other one took me a while to think through, because it turns out not to be a bug. The Articles list isn’t sorted chronologically. In the wildfire item the list opens at 3:21 AM, jumps back to 11:58 PM the previous night, then forward to 7:32 AM. The times are all there and the order ignores them.
Nobody chose that order. Sources carry a display_order the publisher assigns by walking the cluster’s source indices, and those indices come from fetch_all_sources, which iterates the feed list in sources.py top to bottom. That file happens to be written in rough perspective blocks: the official feeds first, then international and left-leaning, public media, broadcast, specialist and tech, wire, business, political trade, with the right-leaning outlets in a block near the bottom. The panel inherits that grouping without a sort ever being written.
That list has a first job, though, and the inherited order happens to serve it. The toggle above the panel reads “16 sources, 6 perspectives,” and the significance model weights editorial diversity over raw outlet count, so what the panel most needs to show is spread — whether a story is carried across the spectrum or only by one wing of it. Contiguous blocks by camp show that at a glance.
The order arrived by accident. It stays on purpose, because the two jobs aren’t worth the same. Spread is the one a reader can’t rebuild for themselves; you’d have to tally outlets by camp to see it. Sequence they can rebuild, because every timestamp is printed right there next to the outlet. And the wildfire item is the reminder that on most days reading them in order tells you nothing: a leak or a managed release is the exception; editorial breadth is every item, every day. Given one axis to sort on, it should be the one that can’t be recovered by eye.
The Receipt for the Receipt
Most readers will never open the evidence panel. Of the ones who do, most won’t cross-reference the timestamps against the timeline. I’m fine with that. What matters is that the brief can be audited, by anyone, on the spot, without asking me for anything. The evidence ships with the item, every item, whether or not a single reader opens it. It isn’t a corrections page written after somebody catches you.
Confidence and agreement are the system’s judgments, and they’re labeled as such. Common ground and key disagreements are the system’s reading of the sources. The two clocks are the closest thing in the product to raw material: here is when it happened, here is when we heard, here is the gap between them. Everything else in the item is built on top of those, and if the assessment and the timestamps disagree, the timestamps win.
That’s a smaller promise than “trust us.” It’s the only one an automated brief has any standing to make.
The Citizen’s Daily Brief is a free daily intelligence briefing from Stalefish Labs.
Want more like this?
Subscribe to get new posts from the lab delivered to your inbox.
or grab the RSS feed