Nadia had shipped the product. What she couldn’t ship was the part where a brand-new user, alone in an empty account at 9pm, figured out what to do first. That part kept landing on the engineering backlog, and the engineering backlog kept winning.
Every onboarding tweak she wanted, a reworded tooltip, one more step, a nudge for users who stalled, was a ticket. The tour lived in the codebase, so changing it meant a pull request, a review, and a deploy. A one-word copy fix took three days and two other people.
The empty room
New users signed up and hit a blank slate. A meaningful share never came back. The team knew the shape of the problem, activation, but the fix always felt like real work: design the flow, get eng to build it, wait for the release train. So the empty room stayed empty, quarter after quarter.
The uncomfortable truth was that onboarding is content, not infrastructure. It changes weekly. It should never have been coupled to the deploy pipeline in the first place.
Onboarding that needs a deploy
Hand-built tours rot in a specific way. Someone renames a CSS class or moves a button, and the tour that pointed at it silently breaks. Nobody notices until a user reports a tooltip floating in empty space. The tour that was supposed to reduce support load quietly became a source of it.
And because every change was a deploy, iteration died. Nadia had a list of onboarding experiments she wanted to run. She ran none of them, because each one cost an engineer a day.
Flows that trigger themselves
She moved onboarding out of the codebase. A guided flow now lives in the dashboard as its own thing: it fires on its own when the trigger matches, a URL like /app/*, a tracked event like project.created, or a manual call from her own code. No update to attach it to, no deploy to change it.
Each flow carries its own audience and its own frequency. Show the welcome tour to new users only, once. Show the “you have not tried this yet” nudge to returning users who never fired the event. The rules that used to be scattered across custom code now sit next to the copy they govern.
Bind once, don’t break
The refactor problem got solved once, at the source. Instead of pointing steps at brittle CSS selectors, the flow points at named elements the app registers: signalpad.registerElement("invite-btn", ref). Move the button, restyle it, wrap it in three new divs. As long as the ref is still registered, the tour still lands. The binding survives the rewrite.
| What you do | Hand-built | Signalpad |
|---|---|---|
| Change the copy | Ship a deploy | Edit in the dashboard |
| Target by plan or segment | Custom code | Built in |
| Trigger on a page or event | Custom code | Built in |
| Survive a UI refactor | Usually breaks | Bound to registered elements |
| Measure completion | A separate tool | Built-in funnel |
The activation curve
Because the same system runs the tour and watches the events, every flow has a funnel: started, each step, completed. When a step bleeds users, Nadia can see exactly which one, and fix the copy that afternoon without asking anyone. Onboarding finally moved at the speed of the person who owns it.
“The tour used to be a quarterly project. Now it’s a Tuesday.”
That is the whole shift. Onboarding stopped being something engineering builds and started being something the product team runs, changes, and measures on its own.