Escape Hatches - discussion (2023-02-05)
The Escape Hatches page is a good counterweight to "everything must be pure" takes. Real apps need focus management, measurement, and sometimes imperative coordination. The part I care about is: can you make escape hatches predictable and observable, so they don't become a shadow architecture?
What escape hatches do you consider acceptable in production (focus, measurement, imperative handles), and what do you avoid? How do you keep imperative work from running on every render and turning into a performance problem? Do you log/label escape hatch behavior as evidence so bugs are diagnosable from screenshots?
Comments (12)
Back to latestFocus and measurement are fine if they're bounded to intent boundaries.
If you measure on every render tick, you're not escaping—you're just making render unpredictable.
We log imperative operations as contract lines and it made the system survivable:
txt
[tips] imperative op=focus target=search reason=routeMotion:list->detail
[tips] imperative op=measure target=panel reason=resize width=1024 height=768
If you can't write a reason, you probably shouldn't be doing the imperative thing.
Counterpoint: it's easy to over-instrument escape hatches and end up shipping debug surfaces as product requirements.
We keep the evidence behind a debug toggle and keep user-facing UI minimal.
Same. The evidence is mostly for support and engineering.
But I still like it because it forces escape hatch behavior to be explainable.
Imperative handles are fine if you treat them like APIs and document them.
If the handle has methods nobody can explain, you've built a private framework.
Long-form take: escape hatches are inevitable; the question is whether they have an identity boundary.
If you centralize measurement and focus into one module and route it through intent, the app stays predictable.
We introduced a small gate for imperative work (cooldown) so a loop can't spam focus/scroll. And we log gate blocks so it doesn't feel like the UI ignored input:
txt
[tips] gate=imperativeFocus allowed=false reason=cooldown remainingMs=140Counterpoint: some imperative work is best-effort (like scroll restoration).
If you try to make it perfect, you end up with a bigger shadow system than the thing you're trying to avoid.
We treat DOM measurement like a dependency: only re-measure when identity changes.
Otherwise the UI will stutter under load and it's hard to debug without logs.
Long-form counterpoint: escape hatches can be used as a crutch for a model that's too complex.
If you need imperative patches everywhere to make the UI behave, the model is probably unclear. The fix isn't more escape hatches, it's a clearer contract.
Docs tie-in: evidence + testing helps keep escape hatches honest.
Related: Testing and Debugging and Performance and Rendering (Best Practices).
The page convinced a couple folks on my team that imperative isn't evil, it's just expensive.
If you pay the cost, you should at least get the value: predictable behavior, bounded scope, visible evidence.
If you want a rule: no imperative call without a reason string.
That simple constraint prevents most runaway escape hatch usage.