The BACK button that kept walking out of the app
On the showroom TV, BACK is the browser going back. A few presses in a row could skip past the app and land on an old cached page. Banking one history step per real press, and loading every page fresh, fixed it.
The problem
A few BACK presses in a row could leave the showroom app for an old cached page.
The solution
Bank one history step per real button press, and serve every page fresh.
On a laptop, BACK is a button nobody presses by accident. On a TV it’s one of five buttons on the remote, and visitors press it all the time. In the browser on AHA’s showroom TV, the remote’s BACK is simply the browser going back through its history. Press it enough times and you leave the app.
The first fix, and why it failed
The obvious answer is to keep a reserve of history steps inside the app, so each BACK spends one of those instead of leaving. The app did exactly that: at start-up it laid down a stack of its own history entries, and each BACK stepped back inside the app.
It worked on a laptop. On the TV, a few BACKs in a row could still jump over the whole reserve and land on whatever the TV’s browser had open before. On the showroom TV, that was the retired desktop map, reopened from the browser’s cache: an old version of the very app we had replaced.
The reason is a browser rule, not a bug in the app. Chrome won’t let a page trap the BACK button. History entries that a page adds without a button press behind them can be skipped when the user goes back, so a page can’t fill history with junk to keep people in. My reserve was laid down at start-up, with no press behind it, so Chrome was entitled to skip all of it.
The fix: earn every step
The rule tells you the way out: history entries are kept when a real interaction created them. So the app now banks one step for each button press or click, up to 40, and a BACK never adds one.
// every real press earns one step of history (BACK spends, never earns)
onRemoteKey(key => {
if (key !== 'BACK' && reserve < 40) { history.pushState({ app: true }, ''); reserve++; }
});
window.addEventListener('popstate', () => { reserve--; stepBackInsideTheApp(); });
In normal use the reserve never runs out, because every move along the rail, every OK and every page opened banks another step. Only a long unbroken run of BACKs, with nothing else pressed in between, can get past the app.
Then make the exit harmless
The second half of the fix is about what happens when BACK does get through. The real problem was never leaving the app; it was landing on a stale copy of an old one.
A small service worker now makes every page on the site load fresh from the server, BACK included, never an old copy from the browser’s cache. So even a determined run of BACKs finds the current app, not the retired desktop map. Closing the tab or typing another address still leaves, and the remote’s HOME button still exits, as it should.
What I took from it
- On a TV, browser history is part of your UI. Test BACK the way a visitor presses it: five times, fast.
- Browsers protect BACK from pages, and they’re right to. Work with the user-activation rule, not around it: history earned by a real press is history the browser keeps.
- Plan for the escape, not just the trap. If a user can leave, make sure where they land is fresh.
- Old versions linger in caches. Retiring a page isn’t done until no cache can bring it back.
It’s a small fix in a few dozen lines, but it’s the difference between a showroom demo that behaves and one that drops a visitor onto last month’s app.