Skip to content
Back to the workshop

Back Is Not a Direction

A return control should reconstruct where you stood

E
EugeneBuilding Cleo
5 min read

A user told me that the product kept loading old things they had been working on. They would open a piece of content, decide it was not the one they wanted, go back, and land on something they had touched days ago. It read like a memory bug. Some stale state being held somewhere and replayed at the wrong moment.

I went looking for the bad state. There was none. Nothing was being remembered wrongly, because nothing was being remembered at all. The bug was the opposite of what it looked like.

The ghost was the default

Here is what was actually happening. The content list is driven entirely by the URL. Every filter, every search term, the sort order, the page number, all of it lives in the address. That part was correct. The problem was the way out. The "back" control on a detail page was a fixed link to the top of the content section. It did not return the user anywhere. It forward-navigated to a default route.

And the default list sorts by most recently updated. So when the user pressed back, they were dropped onto a list whose first card was, almost always, the very thing they had just been working on. Not a haunting. Not stale memory. A default view doing exactly what it was built to do, surfacing recent work, at a moment when the user expected to be returned to where they had been.

The bug was an absence wearing the costume of a presence. It looked like the product was remembering too much. It was remembering nothing, and the default sort filled the silence with the user's own recent activity, which their eye read as a ghost.

A control that forward-navigates is not a back control

The deeper mistake was calling a fixed link "back". Back implies return. It implies that the place you came from is the place you will land. A link to a constant route is a second front door with an arrow drawn on it. It always goes to the same place regardless of where you were, which is the one behaviour a back control must never have.

The fix was to make the forward links carry their origin. Every card, row, and menu item that leads into a piece now passes the current list address along with it, the filters, the search, the sort, the page, all of it, encoded into the link. The detail page reads that origin and uses it for the return. Press back now and you land exactly where you stood, with the same filter applied and the same scroll position in the list, not on a default that happens to show your most recent thing first.

Why the URL, and not the browser's own back

There is a tempting shortcut here, which is to lean on the browser's native back, the history stack the platform already maintains. I did not, and the reason is worth stating because it generalises.

History-stack back is fragile in exactly the situations that matter. Reload the page and it can be gone. Arrive cold from a shared link and there is no history to go back to. Land on the detail page from a notification and back means something the user never did. Carrying the origin in the URL is none of those things. It is deterministic, it survives a refresh, it works when someone opens the link cold, and it can be shared. The destination is written down, not inferred from a stack that may or may not exist.

There is one cost, and I flagged it to myself plainly. An address that is handed around as a link is a thing an attacker can craft. So the detail page does not trust the origin it is given. It validates it as an internal path before using it, rejects anything that tries to point outward, and falls back to a safe default if the value looks wrong. Carrying state in a URL is convenient and it is also a surface, and the surface has to be guarded at the point where the value is used.

What this taught me to check first

The lasting lesson was diagnostic, not technical. When a user describes stale state, my first instinct now is not to hunt for what is being remembered wrongly. It is to ask whether anything is being remembered at all. Sometimes the ghost is not bad memory. It is no memory, and a default view stepping in to fill the gap with something that looks personal because it is, in fact, the user's own recent work.

The repair, once I understood it, was almost boring. Make the way out carry the way in. But I would not have found it while I was still looking for a haunting, because the bug was hiding inside an absence, and absences are hard to see when you are certain something is there.

E

Written by Eugene

Building Cleo, an AI marketing operating system. These posts cover the architecture decisions, technical challenges, and lessons learned along the way.

More from the workshop