Every authenticated page must live inside the shell
A page that "works" but drops the navigation around it is a regression even when every test is green.
Apollo Space Research
Apollo Space
You click into a detail page and the sidebar vanishes. The org switcher is gone. The header with your account and your search is gone. The page itself renders fine, the data loads, the buttons work, the tests are green, but you are now standing on a single floating panel with no way back except the browser’s back button. You didn’t leave the app. The app left you.
Every team that ships a product with a navigation shell hits this, and most hit it more than once. A new page gets built as its own top-level route, it passes review because the page works, and it quietly tears a hole in the product the first time a real person navigates to it.
A page that “works” but drops the navigation around it is a regression even when every test is green.
This post is about why that keeps happening, why your test suite is the last thing that will catch it, and the one rule we made non-negotiable to end it.
The naive version: a page is a route, a route is a page
The obvious mental model is the one every router teaches you on day one. A page is a URL. You add a page by adding a route. The route points at a component, the component renders the screen, and you’re done. New settings page? New route. New detail view? New route. It’s clean, it’s composable, and it maps perfectly onto how the framework thinks.
It also works flawlessly right up until the moment it doesn’t.
The trouble is that a route renders whatever you point it at, and nothing more. If you point a top-level route at a bare page component, the framework will happily render exactly that: the page, alone, filling the whole viewport. The sidebar, the org context, the header, all the framing that makes the screen feel like part of an application, only exist if some parent component put them there. A top-level route has no parent doing that. So the page renders, the tests confirm it rendered, and the navigation is simply absent. Not broken. Absent. Which is worse, because absence doesn’t throw.
Here’s the part that makes it durable: the bare page is the path of least resistance. It’s faster to scaffold a standalone route than to thread a new screen into an existing layout you didn’t write. The tooling nudges you toward it. The demo of any UI component shows it floating in isolation, because that’s how you demo a component. So the wrong version is the one that’s easiest to build, easiest to copy from a tutorial, and easiest to merge, and the cost only shows up when someone clicks.
Why the test suite is the wrong watchdog
You’d think a test would catch a missing sidebar. It almost never does, and the reason is structural, not a gap in diligence.
A component test mounts the page in isolation on purpose, that’s the whole point of a unit test, to check one thing without dragging the universe in with it. So the test renders the detail page by itself, asserts the data shows up and the buttons fire, and goes green. It is correct. It is testing exactly what it was written to test. The thing it can’t see is everything it deliberately left out: the page was always supposed to render inside a shell, and a test that mounts it alone has erased the exact context where the bug lives.
The bug isn’t in the page. It’s in the page’s relationship to everything around it. A page that “works” but drops the navigation around it is a regression even when every test is green, and this is exactly the kind of regression a green suite is built not to notice.
That’s a whole category of defect that unit tests are blind to by construction. The component is individually perfect and collectively wrong. Your coverage number goes up while the product gets a little more broken, because coverage measures whether each piece works in isolation, and this failure is made of pieces that each work in isolation. Green tests on a self-contained page tell you the page is self-contained. They were never going to tell you it shouldn’t be.
This is why “the tests pass” is the most dangerous sentence in front-end work. It’s true. It’s also answering a question nobody asked. The question was never does this page render, it was can a person reach this page, use it, and leave it without getting stuck, and that question only has an answer when you put the page back into the application it belongs to and click around like a human would.
Coverage tells you each part works. It says nothing about whether the parts add up to a place a person can stand.
The rule: the shell frames everything, the page is a guest
So we wrote the rule down and made it carry real weight. Every authenticated page renders inside the application shell, never as a bare, sidebar-less route. The shell is the host. The page is a guest inside it.
Concretely, the shell is the part of the product that never goes away while you’re signed in: the sidebar, the org switcher, the header. It owns the navigation. A new screen doesn’t get to be a sibling of the shell, mounted next to it at the top of the router. It gets mounted within it, as a section the sidebar already knows how to reach, or as a view-state inside an existing framed area. The page inherits its frame for free because the frame was never the page’s job to provide.
This flips the default. In the naive model, framing is something you remember to add, and forgetting is silent. In the shell model, framing is something you’d have to deliberately tear out, and tearing it out is loud. The easy path and the correct path become the same path. You can’t accidentally ship a floating page because there’s no top-level slot to float it in, the page only has a home inside the shell.
There’s one honest exception, and naming it sharpens the rule rather than weakening it. A few authenticated surfaces are meant to be full-screen with no sidebar, a focused inspector you open to study one thing and then close, like a full-page trace view. That’s a deliberate decision, modeled that way on purpose, with a clear way back. It is the opposite of the bug, which is a page that’s sidebar-less by accident and offers no exit. The test for the exception is simple: did someone decide this screen should stand alone, or did it just happen to? If nobody decided, it’s the bug.
How you actually catch it: be the user, on the real surface
If tests can’t see this class of bug, something has to. The thing that sees it is a person, or an agent acting like one, clicking through the deployed product the way a customer will.
The naive idea of “done” for a new page is: it renders, the tests pass, merge it. We replaced that with a harder bar that’s specific to this failure mode. Before a page counts as built, three things have to be true, in order. It has a home in the navigation, you can name the sidebar section it belongs to. It renders within the shell, the framing is there because the shell put it there, not because the page rebuilt it. And someone reached it from the navigation on the running product and confirmed the shell stayed put the whole way: the sidebar didn’t disappear, the way back was obvious, the page was never a dead end.
That last step is the one that can’t be faked. A page can pass every test and still strand a user, and the only instrument that detects stranding is the experience of being stranded, which means actually doing the click, on the real surface, and noticing whether you can get out. We don’t accept “it should work.” We accept “I navigated to it, the sidebar held, and I could leave.” Those are different claims, and only the second one is evidence. A page that “works” but drops the navigation around it is a regression even when every test is green, and the only place that truth becomes visible is the click itself.
It’s tempting to treat this as fussy. It isn’t. A floating page is a small bug that produces a large feeling, the feeling of an app that doesn’t hang together, where one wrong click drops you somewhere the product forgot to furnish. Users don’t file that as a bug. They file it as this thing feels broken, and they’re right in the way that matters most, which is the only way that matters: how it felt to use.
The turn: an application is a place, and a place has to hold
What is a product, really, underneath the features?
It’s a place a person can move around inside without getting lost. Every serious application, the ones that feel solid, the ones you trust with real work, shares one quiet property: wherever you are inside it, you can always tell where you are and always see how to get somewhere else. The navigation is always there. That constancy is not decoration on top of the product. It is the product, in the sense that it’s what turns a pile of screens into a single coherent place you can inhabit. Take it away on one screen and you haven’t shipped a page with a minor flaw. You’ve punched a hole in the floor of the place, and the person who steps in it stops trusting the floor.
That’s why this rule earns its non-negotiable status while fancier rules don’t. It’s not about a sidebar. It’s about whether the thing we’re building holds together as a place, whether a person can wander into any corner of it and still feel held by the same frame, still see the way home. A page that renders perfectly and breaks that feeling has failed at the one job a screen in an application has, which was never just to display its data. It was to be a room in a building you can find your way around.
The features will keep multiplying. There will always be one more page to add, and the fast way to add it will always be the floating one. The rule holds anyway: the page lives inside the shell, or it doesn’t ship, because the moment we let a screen drop the frame to save an hour is the moment the product stops being a place and starts being a pile.
That’s what we’re building at Apollo Space: an operating system for real work, where every surface you reach still feels like the same building you walked into. If you’ve ever clicked one link too far and found yourself standing on a page with no way back, you already know why the shell isn’t the part you skip, it’s the part that makes the rest a place worth being.
Apollo runs your company's repetitive ops so your team doesn't.
Join the waitlist for early access, founding-user pricing, and a front-row seat as we ship.
Join the waitlistThe hidden tax of parallel agents is a migration diamond
Six agents writing to one schema conflict in the database, not the code, and CI dies at "multiple heads."
EngineeringAn orchestrator that can't survive its own crash isn't one
A crash that erases the orchestrator's reasoning loses the one thing you can't rebuild.
EngineeringPut a deterministic gate in front of your smartest reviewer
The cheapest defect-catch is a dumb script that checks two merged branches still boot before any judgment.