You are a third of the way down the front page. A headline caught your eye two lines below your thumb, and you’re just moving to tap it. In that half-second — the gap between deciding to tap and the tap landing — the wall changes. A story broke; the editor added three headlines at the top. DReader, knocking on the page at its own honest pace, catches the change and redraws.

If it redraws the obvious way, your tap lands on the wrong story. The three new headlines took up room at the top, so everything below them — including the line you were aiming at — slid down by exactly that much. Your thumb, which never moved, comes down on a headline that was three rows lower a moment ago and is now sitting under your finger. The refresh worked perfectly. It fetched the new front page, it rendered it, it lost you your place, and it cost you a mis-tap on the way out.

This is the tax nobody quotes you when you set out to keep a page live. Fetching the change is the part that looks like the work. Applying the change without yanking the reader is the part that decides whether “live” feels like a courtesy or an ambush.

Scroll position is a number that keeps changing meaning

The reason the naive redraw betrays you is that the browser doesn’t remember where you were in the content. It remembers scrollTop — a count of pixels from the very top of the column to the top of your screen. That number is only meaningful relative to whatever sits above it, and the instant you insert three headlines up there, the same scrollTop points at a different place in the story. You haven’t scrolled. The ruler you’re measured against just got longer at the end you can’t see.

So a reader that re-renders the front page and leaves the scroll position alone isn’t being lazy — it’s faithfully preserving a number whose meaning it just destroyed. Everything visually jumps down by the height of what was added above the fold. On a quiet update that’s one headline and a small hop; on a breaking-news flurry it’s a lurch of half a screen, right at the moment the page is most worth reading and the reader is most likely to be mid-tap.

The browser keeps your pixels and forgets your place. The two are the same only until something changes above the fold — which, on a live front page, is the whole point.

Anchor to a headline, not to a pixel

The fix is to stop preserving the pixel and start preserving the headline. Before applying an update, DReader looks at what’s actually on screen and picks an anchor: the first headline whose row is at or just below the top edge of the viewport — the line the reader is most plausibly reading. It records one number, the anchor’s offset from the top of the screen. Then it applies the new front page. Then it reads the anchor’s position again, and scrolls by the difference, so that same headline is sitting exactly where it was. New stories can pile in above it all they like; the line under the thumb holds still.

The whole trick rests on being able to find “that same headline” in the new page, which is only possible because DReader already refuses to treat a headline as its text. A headline is keyed by where it points, not by what it says — the same discipline that keeps a story from looking new just because the editor reworded it. That stable identity is what lets the anchoring survive a full redraw: the row can move, change wording, gain a sibling above it, and DReader can still say that one, the one you were reading, put it back there.

Browsers do ship a native version of this — CSS overflow-anchor, which tries to hold your position when content shifts above the viewport. It’s a good default and it’s on by default. But it’s built for content that grows, not content that is replaced: swap the list out from under it, hand it rows whose DOM identity it can’t match to what was there before, and it has nothing to anchor to and quietly gives up. A live front page that re-derives its rows on every fetch is exactly the case the native mechanism can’t cover, which is why DReader keys the rows itself and does the last step by hand.

New goes in above the fold, and the count goes in a pill

Holding your place solves the jump, but it raises a fair question: if the three new headlines slot in above where you’re reading and you never move, how do you find out they’re there at all?

You get told, quietly. New stories accumulate above the fold, and a small tab settles at the top of the screen — 3 new ↑ — that you can tap to jump up when you’re ready, or ignore while you finish the line you’re on. This is the same instinct every well-behaved live feed has learned: the reader owns the viewport, and news arriving is an offer, not a command. Yank someone to the top because something broke and you’ve decided for them that the new thing matters more than the thing they were already reading. Set a count and let them reach for it, and refreshing costs them nothing until they choose to spend it.

It’s a small piece of interface, but it’s the whole ethic of the reader in miniature. DReader exists to take a busy wall and make it calm to read; a refresh that grabbed you by the scroll bar every time the editor twitched would be the opposite of that. The count in the corner is how the page stays live without ever being loud.

Where it gets hard, and stays honest

The clean version above is the version that works when the reader is parked mid-column with a stable row under the fold line. Real reading is less tidy, and the seams are worth naming.

The anchor can scroll off. If the reader has flicked into a fast momentum scroll at the exact instant the update lands, the row you measured may already be gone by the time you go to restore it; you have to choose the anchor from where the viewport will be, not where it was, or accept a small correction and move on. The anchor can also change under you — the very headline you pinned to is the one the editor just pulled or reworded — so the restore has to fall back gracefully to the nearest surviving row rather than snapping to nothing. Measuring positions before and after in the same frame means reading layout the browser would rather compute lazily, so it’s done deliberately, in one batched pass, rather than scattered through the update where each read would force another reflow. And images are the quiet saboteur: a thumbnail that finishes loading after you’ve restored the scroll adds its height late and reintroduces the very jump you just prevented, so rows reserve their space up front and don’t get to resize once they’ve been placed.

None of these are exotic. They’re the ordinary friction of holding a position steady on a surface that’s allowed to change shape, and the honest answer to each is a conservative default: anchor to something that’s likely to survive, restore to the nearest thing that did, and never let a late-arriving pixel move a line the reader’s eye is already resting on.

The demo is the front page updating live, and it’s a good demo. The product is everything that has to be true so that the update arrives without the reader ever feeling it — three new stories up top, a quiet 3 new ↑ in the corner, and the line you were reading exactly where you left it.