Architecting product tours in React: How we moved fast without leaving a trail of tech debt

Architecting product tours in React: How we moved fast without leaving a trail of tech debt

One of the most critical areas of Product Engineering that we have at Asana is First Experience (FX). The FX team’s mission to make users’ first seconds in Asana flawless. This team’s work is so crucial to our growth because the first few moments that someone uses Asana for the first time can make or break their propensity to continue using the product. With fewer than 10 members, the FX team knows how to have a big impact in a short amount of time.

Today, any new Asana user will have a guided, seamless experience to help them understand the most important parts of the product in the quickest amount of time. But it wasn’t always this way:

The first time you used Asana, there was something important that you probably missed. It was an egg, hidden in plain sight.

A complex set of algorithms create this egg. As you were signing up for Asana, these algorithms were making deductions about you. They were asking,

Who is this person?

What is their role on the team?

What do they need to do in the first 60 seconds of using Asana to make their team successful?

The algorithms balanced a dizzying set of data points and inputs to make a decision. And then they laid an egg.

A NUX egg, to be precise. At least, that’s what we call them (the “NUX” stands for “New User Experience”). It’s a rounded arrow colored in a cheery shade of orange-coral (or “corange”). The arrow points to the first thing that you should do when you use Asana. It oscillates gently to draw your attention. Follow the trail of NUX eggs, and you’re far more likely to achieve great things with Asana. They’re kind of a big deal.

But we know that you probably didn’t follow the NUX eggs.

And that’s because almost no one does. Asana has displayed these tours for years, but only one percent of new users have ever completed them. The other 99 percent ignore the NUX eggs. They’re left to hover sadly in the product, their purpose unfulfilled.

This was the problem that the First Experience (FX) team faced earlier this year.

It wasn’t going to be easy. The codebase supporting NUX eggs was brittle and difficult to change. There were a dizzying array of different use cases to support. And it was all written in a legacy framework that was no longer supported or well-understood.

1. Experimenting in the legacy framework

The team started to plan. Most of Asana’s application frontend is written in React and Typescript. That stack provides a lot of stability and performance. But not the NUX eggs. The NUX eggs are written in Luna1, Asana’s legacy web application framework. Few Asana engineers are proficient in Luna1 these days, which makes those features pretty hard to change.

And change was exactly what the team needed. There was still a lot that they didn’t know about why users didn’t engage with the NUX eggs. To find out, they would need to run several experiments. Those experiments would help them learn how to optimize the user experience.

Running all these experiments on Luna1 code was dangerous. The last thing the team wanted was page-breaking bugs. It was crucial that a user’s first experience was stable. The new stack had better static analysis and testing in place. Experimentation would be much safer in that environment.

But porting the NUX egg to the new stack was a huge risk. The team didn’t know whether any of the experiments would show improvements in user engagement. What if the best answer was to not have a product tour at all? That would mean weeks of wasted effort in architecting a new system.

The FX team thought deeply about these pros and cons. They then decided on a counter-intuitive solution. They would run an experiment on top of Luna1 first. Then, if the results looked promising, they would invest in long-term architecture.

They introduced a new React component called the IPE Bar (“in-product education”). The IPE Bar is a purple box that slides up from the bottom of the screen while the user sees the NUX Egg. It lists instructions to reinforce the action that the NUX egg is asking the user to perform. The tour would seem to take over the whole screen.

The IPE Bar was written in React and Typescript. However, it still needed to be able to talk to the Luna1 NUX eggs implementation to stay in sync. The FX team built a bridge that connected the IPE Bar to the legacy application state stored by the NUX eggs. The NUX eggs, for their part, didn’t need to undergo any changes at all for the experiment. Well, besides a simple new purple coat of paint.

They ran the AB test for several weeks before looking at the data. The results showed a significant improvement in user engagement metrics. The experiment worked.

2. Reimagining the architecture

Product tours are gnarly. They need to form a coherent experience. Each step and arrow behaves in a uniform, predictable way. A given arrow or indicator knows to disappear when the button it’s pointing to is clicked. To do this, they also need to be aware of the internal state of many different parts of the app.

How do you reconcile those two things?

The NUX Eggs tried a clever approach to this problem. NUX Eggs encode a list of DOM selectors pairs with names of log events. A given arrow would search for the DOM element (something like #new-task-button). It would then wait for a log event to trigger (for example, NewTaskButtonClicked). Then it would mark that step as completed and proceed for the next arrow.

This seemed like a good idea at first. But this means the elements of the UI have no explicit knowledge of the product tour. So, if you changed a UI element, its NUX eggs would get lost and confused. The UI elements were implicitly coupled to the implementation of the product tours. The bugs were frequent and annoying to fix.

The FX team learned from these problems. They addressed them in a new product tour system called Coachmarks. Coachmarks don’t use DOM selectors to find elements. Instead, they use a React component which wraps around the target element.

This more explicit coupling requires a bit more boilerplate. This is a good thing. It means that engineers are far less likely to break coachmarks by accident.

They no longer relied on log events to dismiss Coachmarks. Instead, they used an explicit Coachmark service object. When the user clicks the button, you invoke an explicit method to dismiss the coachmark. This explicit coupling makes it a lot harder to break a product tour by accident.

The Coachmark system doesn’t completely solve the problems inherent to product tours. But it makes these problems easier to predict and debug. All you have to do is follow the explicit display and event logic.

3. Instrumenting for high performance

With the Coachmarks system in place, the FX team was ready to launch some brand-new product tours. But one tour they worked on required the application to load a bit more data to initialize itself.

Asana Engineering has a very high standard for application performance. We selected the new stack with performance as a top priority. And many teams spent considerable effort using the new stack to help Asana load twice as fast last year. Asana is fast for now, but it doesn’t take many performance regressions to slide back into poor performance.

Aigerim Karabekova, an engineer on the FX team, wanted to prevent these performance regressions. So, she added performance instrumentation to her new feature. She especially wanted to confirm that this additional data loading had a negligible change in load times. So she had to dive deeper into how we loaded data.

This spring, Aigerim had only been at Asana for a few months. She was an experienced engineer, but she had never worked on a system quite like Asana’s. To be successful, she needed to learn the nuances of Asana’s data loading architecture.

Asana loads its data in groups called projections. But if Aigerim had simply added her new field to the projection, she wouldn’t have been able to AB test the performance of her change. The new data field would have always been loaded, whether the user was in the experiment or not.

To solve this problem, Aigerim needed to make an unusual parameterized projection. This is a projection that conditionally loads one of its sub-branches. With that in place, she was able to only load the data when it was needed for a particular experiment.

Aigerim launched her new Coachmarks tour to real users in production. She then carefully analyzed the performance characteristics. She found that users seeing her new tour did not experience slower load times than other users.

Coachmarks and guideposts

These days, 30 times as many users complete their Coachmarks tour than ever completed the old NUX eggs tour. And the FX team has a solid technical foundation for product tours. Now, they can experiment with an endless variety of new tours in the future.

The team was able to manage the risk of their changes by testing their theories early in development. They also kept a commitment to sustainable architecture and reducing tech debt. By striking this balance, they were able to improve the product while also paving the way for years of experimentation in the future.

If you’re interested in working on challenging technical problems like this one, then you should work with us at Asana!

Special thanks to
FX team members who worked on Coachmarks, including Marc Baselga, Matt Bond, Kevin Hong, Aigerim Karabekova, Viveka Mastandrea, Alvaro Morales, Edward Nguyen, Joshua Penman, and Zen Sun.