Skip to content
9 min read

Status is not freshness — the field the API sent and the dashboard threw away

Every dashboard that confidently shows a position that is no longer true has a near-miss inside it. The freshness data usually already arrived from the API. Somebody wrote a guard, and put it on the wrong axis.

Vyacheslav Pankratov· Fullstack Developer
Cover art for “Status is not freshness — the field the API sent and the dashboard threw away”
On this page

TL;DR: A stale position on a live map is almost never a missing-data problem. The age information has usually already arrived from the API and is sitting unread, and somewhere in the client there is a guard someone wrote deliberately to avoid drawing a bogus marker — pinned to the wrong axis. In the system I know best that guard is the order's status: draw the truck while the order is in transit. A phone that died six hours ago renders exactly like one reporting every minute, because the order is still in transit either way.

The interesting thing about dashboards that lie is how rarely they lie out of ignorance.

I went looking for why ours could show a driver somewhere they were not, expecting to find that the information simply was not there. It was there. The API returns a last-update timestamp on every order. The server writes it faithfully. And across the whole client there is not one reader for it — no label, no tooltip, no styling that changes, nothing. The data arrives, is deserialised, and is dropped on the floor.

It is worse than an oversight, because the server does compute an age from that field — once, with a hard-coded threshold, inside a helper that guards a cancellation. So the arithmetic exists, the constant exists, and neither ever reaches the surface where a human is looking at a map. Somebody wrote the freshness logic. It just went somewhere nobody would ever see it.

The near-miss is the tell

What makes this worth writing about is not the omission. It is that somebody did think about it.

There is exactly one guard on the marker, and it is not lazy: the truck is drawn only while the order is in an active state — loading, or departed. Someone sat down, considered that a marker could be wrong, and wrote a condition to stop it. That is the opposite of carelessness. It is the shape real systems actually fail in: not an absent thought, but a thought that reached for the nearest available axis and stopped there.

The two facts, side by side:

Order status Freshness
Answers is this delivery still happening when did we last hear from the device
Source an enum on an object already in hand a subtraction against the current clock
Changes when a human or a workflow moves it silence accumulates
Wrong when never — it is authoritative never — it is arithmetic
Substituting one for the other is wrong when the device goes quiet while the order stays open

Order status is a lifecycle fact. Freshness is a temporal fact. They correlate strongly enough in normal operation to look interchangeable, which is exactly why the substitution survives review. Every test passes. Every demo works. The two facts only come apart when the device stops reporting while the order is still open — which is the one case the guard exists for.

Why does status look like freshness in the first place?

Because status is available and freshness is derived, and the difference in effort is roughly one line against a conversation.

Status arrives as an enum on an object the component already has. It is discrete, it is easy to reason about, and a condition written on it is obviously correct at a glance. Freshness is a subtraction against the current clock, which means a decision about a threshold, which means deciding what threshold — and that is not an engineering question. Nobody wants to invent a number in a component file, so the guard quietly becomes the thing that needs no number.

There is a second reason, and it is structural. The socket event that updates the position carries only an id and a pair of coordinates. No timestamp. So the component that receives live updates is structurally incapable of knowing how old a position is, even in principle, from the stream that updates it. The freshness field only exists on the REST object fetched when the page loads. Two sources, one of which knows the time and one of which does not, and the live path is the one that does not.

The web already standardised this, and we did not look

The thing that annoys me most about this failure is that HTTP solved it decades ago and published the vocabulary.

RFC 9111 defines a response's freshness lifetime — how long it may be treated as current — and its age, the time elapsed since it was generated. A response past its freshness lifetime is not deleted and not hidden. It becomes stale, which is a first-class state a cache can reason about: serve it and say so, revalidate, or refuse. The Age header exists precisely so a consumer can decide rather than guess.

That is the entire design we needed, expressed in a specification everyone building web software has already met. A position has a generation time and an age. Past some lifetime it becomes stale. Stale is not "hide it" — it is "keep showing it and tell the truth about it", because a dispatcher who knows the last known position was twenty minutes ago is far better served than one shown nothing, and infinitely better served than one shown a confident dot.

Stale is a state to render, not a reason to hide

RFC 9111 has the vocabulary and most dashboards ignore it: a response past its freshness lifetime is not deleted, it becomes stale — still served, still useful, and labelled. A last known position works the same way. Hiding a marker throws away the most valuable thing you have at the exact moment somebody is looking for it, and it makes the fleet look smaller than it is.

How stale is too stale?

Not an engineering question, and pretending otherwise is how the guard ended up on the wrong axis.

A truck on a motorway invalidates its position in seconds — at 90 km/h a two-minute-old fix is three kilometres of error. A truck parked in a yard is exactly where it was an hour ago. A phone reporting every fifteen minutes by design is healthy at fourteen minutes and broken at forty. The same elapsed time means completely different things, and no threshold baked into a component can know which case it is looking at.

In code the difference is one predicate, and it is worth seeing how small it is:

// what we have: a lifecycle guard standing in for a temporal one
if (order.status === "DEPARTED" || order.status === "LOADING") show(marker)

// what it should be: age, with the threshold injected rather than invented
const age = Date.now() - Date.parse(order.lastPositionUpdate)
show(marker, age < cfg.fresh ? "fresh" : age < cfg.stale ? "ageing" : "stale")

So the system's job is not to pick the number. It is to surface the age and let the threshold live where the domain knowledge is — configuration, per vehicle class, per customer, adjustable without a deploy. Three states are usually enough: fresh, ageing, stale. What matters is that the third one is visually distinguishable from the first at a glance across a screen of forty vehicles, because that is the actual job — not inspecting one marker, but noticing which of forty has gone quiet.

And the honest default when you have no threshold yet is simply to print the age. "Last seen 14 min ago" under the marker requires no decision from anyone and removes the entire class of failure this article is about.

What a map may claim

A marker on a map is a claim in the present tense. Drawing a truck at a coordinate asserts that the truck is there now, and the assertion is made by the rendering itself — not by any text next to it. There is no way to draw a confident icon and mean "probably, twenty minutes ago". The visual language does not have a hedge.

So either the claim is supported, or the rendering has to change. Fading the marker, hollowing it, attaching an age label, moving it to a separate "not reporting" list — the specific treatment matters much less than that something differs. What cannot happen is for the two states to be pixel-identical while the underlying certainty differs by six hours.

  1. Send a capture timethe live path, not just the fetch
  2. Compute the agenow minus capture, per entity
  3. Classify against configthe domain owns the number
  4. Render three statesvisible across forty markers
The order matters more than the threshold. Every dashboard that lies got to step three without doing step two.

FAQ

Our dashboard shows "last updated" at the top. Is that enough? Usually not, because it is a page-level claim answering a different question. A timestamp in the header tells you when the fetch happened, which is a fact about your own request, not about any device. With forty vehicles on screen, one silent device is invisible behind a page timestamp that keeps refreshing cheerfully. Freshness has to be per-entity, because the failure is per-entity.

Why not just hide markers that have gone stale? Because the last known position is genuinely valuable — it is where you start looking. Hiding it throws away the most useful thing you have at the exact moment someone needs it, and it also makes the fleet look smaller than it is, which hides the problem rather than surfacing it. Stale means say so, not disappear. RFC 9111's cache semantics get this right: a stale response is still served, labelled.

We do send a timestamp in the position event. Are we fine? Check that something reads it, and check the live path specifically. Ours sends one on the REST object and not on the socket event, so the code path that updates a position in real time is the one without the time. That asymmetry is easy to introduce and invisible until you look for it, because the page renders correctly on load and only drifts afterwards.

Where should the threshold live? Outside the component, and ideally outside the build. Per vehicle class or per customer, in configuration you can change without shipping code — because the first threshold anyone picks is wrong, and the value of the whole feature depends on being able to correct it after watching real operators use it. Ours is a bare constant inside a helper, which is how you get a number nobody can find and nobody dares change.

The three things worth taking away

  1. Check whether the freshness data already arrived before building anything. In our case it had, and had done for as long as the endpoint existed. The failure was on the read side.
  2. Status is not freshness. A lifecycle enum and elapsed time correlate until the exact moment they matter, which is why a guard written on the wrong one passes every test.
  3. Surface the age; do not pick the threshold in a component. A vehicle on a motorway and one in a yard decay at completely different rates, and only the domain knows which is which.

The field that should have carried this is the subject of the piece before it — the timestamp that was never on the wire — because a dashboard can only be as honest as its payload allows. The piece after it follows the same data one layer down, into the history table nobody queries, where every position the map ever showed is still sitting.

Where this sits in the larger problem — a field device on a link that drops, and everything downstream of that — is on freight telemetry that survives a bad link.

If your map is showing something you are no longer sure of, that is the kind of thing we come in for.

Was this helpful?

// Build it

Running this in production?

Telemetry fails quietly — a timestamp that never reached the wire, a track you cannot draw, a fleet that reconnects all at once. Tell us what your map is showing and when; we reply within a day with a concrete next step.