On an internal operations tool I built a lot of genuinely rich UI — multi-step wizards, live filters, inline editing — while keeping almost all of the logic on the server with Hotwire. The trap with Turbo and Stimulus isn't the framework; it's the temptation to reach for a global event listener the moment two things need to talk.

Multi-step flows belong on the server

A project-creation wizard went through server-driven steps rendered over Turbo Frames, with the in-progress draft persisted as a real record. Because the state lives in the database rather than the DOM, a refresh, a shared link, or the back button all behave correctly for free — no fragile client-side step machine to keep in sync.

Stimulus outlets over window events

The biggest cleanup was replacing ad-hoc window-event patterns with Stimulus outlets. Instead of one component shouting into a global event bus and hoping the right listener is attached, components declare the connections they need. It reads like wiring, not telepathy — and it's testable.

Keep controllers small and named

Each Stimulus controller does one job — a Trix auto-linking controller, a filter controller — and navigation runs through a single centralized router rather than scattered redirects. Small, well-named units keep the surface area of any change tiny, which is what makes a Hotwire app pleasant to live in months later.