Every time I opened the todo tab in our app, a sheet slid up asking me to write down my intention for the day.
Dismissing it every single time got old, so I turned off the automatic open. One effect, a few lines, five minutes.
That evening I realised there was no longer any way to open that sheet at all.
I disabled a trigger and lost the entry point
I went looking for another entry point and there was none. No button, nothing in a menu, nothing in settings.
It appeared when you entered the screen. That was it.
So what I actually shipped was not “stop showing this”. It was “remove this”. The file is still there, the component still renders correctly, and the type check and linter both pass. Nothing broke. It is simply that nobody can reach it.
This same sheet had caused trouble before, in the opposite direction. It used to open on top of a guide overlay and swallow every touch, which made the app look frozen on first run. Back then it was too eager. This time I quieted it and it disappeared.
I did it again two days later
The todo screen had a calendar icon in the header. I added a today/week toggle next to it, which meant the week view behind the icon and the week view behind the toggle were now doing the same job.
So I removed the icon. Deduplication, obviously correct.
Except the screen behind that icon also had a month view, and that icon was the only route to it. The toggle inherited week. Month lost its door.
The difference is that this time I checked before cutting, so I removed it knowingly and wrote the consequence into a comment.
First one by accident, second one on purpose. Same outcome either way.
Nothing fails, which is why nothing warns you
The build succeeds. Types pass. The linter will not flag the component as unused if something still imports it, and the screen is still registered in the router, so navigate('ThatScreen') opens it fine from code.
It is not unreachable. It is only unreachable by a human with a finger.
Static analysis checks reference graphs. Whether a person can navigate to a screen is not a reference graph question, so adding more tooling of that kind would never have caught either of these.
The check I run now
When I touch a condition that controls whether something appears, I look for other doors first:
git grep -n "ThatScreen" src | grep -v "screens/ThatScreen/"
If nothing outside the screen’s own directory references it, then the condition I am about to disable is the only door.
At that point there are three honest options: build another door before disabling, delete the screen along with the trigger, or disable it knowing the door is gone and record that fact.
All three are fine. What is not fine is disabling it without knowing which of the three you just did.
Automatic triggers are the dangerous case
If you delete a button, your hands remember. You go to press it, it is not there, and you notice.
An automatic popup does not work that way. Its absence is the outcome you wanted, so the loss looks like success. I was genuinely pleased with that change on the day I made it.
There is a second reason automatic triggers are overrepresented here. Because the thing appears on its own, nobody ever built a manual way in. The trigger and the entry point are the same object, so switching it off takes the door count straight to zero.
That makes it a rule of thumb worth carrying: the more automatic something is, the more likely its trigger is also its only entry point.
Why I kept the code
I did not delete either screen.
The first is a reflection sheet, the second is a month calendar. Both were turned off because they were in the way right now, not because I decided they were worthless.
Instead I wrote “currently unreachable” into the test docs, so that reviving either one starts with building an entry point rather than wondering why nothing happens.
That is a preference, not a rule. Deleting immediately keeps the repository cleaner. I traded the cost of rewriting later against the cost of forgetting that it is there, and I picked the second. The note exists to prevent the forgetting, and I do not yet know whether anyone reads it.
Why static checks cannot help here
It is tempting to reach for tooling after a mistake like this, so it is worth being clear about why tooling would not have helped.
A linter can tell you that a symbol has no references. Both of my screens still had references, because the router registers them by name and the imports are all intact. The graph is complete. What is missing is not an edge in the graph but a path a person can walk.
You could build something that walks navigation calls backwards from every registered route to a rendered control, but that means teaching a tool what counts as a control the user can actually press, including ones behind feature flags and permission checks. I have not tried it, and I suspect the false positive rate would make it noise.
For now the manual grep is what I have, and the value is not in the command. It is in remembering to ask the question at the moment I disable something.
Scope
This happened on 2026-08-15 in one React Native app on Expo SDK 56, with two screens becoming unreachable the same way inside two days.
I do not know how common this is in general, and I have not counted how many other unreachable screens my own repository already has. Cross-referencing router registrations against actual call sites looks automatable, but I have not tried it, so I cannot say how accurate that would be.