The past couple weeks have been stressful. Lowest on the list was a Safari performance issue that came out of nowhere upon launching the new Clients section. In Chrome and Firefox, the Clients section is lightning fast. In Safari, however, the more clients in the list, the slower the interactions on the page. Tabbing fields and clicking buttons with a list of a few dozen clients would include a half-second delay—even if the interaction had nothing to do with the list.

While this continued to stump me throughout the workweek, the bigger stress on my shoulders was that my wife and I put an offer on a new home. Making the offer itself wasn’t stressful, but the week long scramble of interviewing lenders, gathering our finances, and imagining our life with much less money gave me a few sleepless nights. I actually had to take a day off of work because I knew I wouldn’t have the time to get everything together while still being able to focus on my actual job. Unfortunately (and fortunately) our offer fell through because another buyer beat us to it. I’m not upset, though. Because we don’t need to empty our bank accounts and sell all of my investments, our life continues as usual, in an apartment we still love, instead of taking an abrupt turn into homeownership in a place we only saw for 10 minutes—chained to a mortgage that would require us to always have a consistent income.

With the offer behind us, I returned to the Safari performance issue that was plaguing me. Initially, I chased a red herring in the form of Vue.js’s reactive data. I ended up there because interactions would fire just fine in their handlers, but the change of a reactive property would incur the delay. This was less about the reactive property and more about what’s happening between the interaction and the property changing—specifically, the layout calculation between frames. I did eventually up there, but only after several more rabbit holes.

My biggest hunch was that the deep composition of the list caused the delay, with each client list item being a component and each cell being its own component. I rebuilt the Clients section to be much flatter by extracting the client list item into the list itself and replacing fields with much lighter “grid cells”. To my dismay (but not really because not much surprises me after coding this long), the issue still existed. I thought for sure that the complexity of the list caused the delay, but it wasn’t until I removed the nested grid from the list that I understood the real problem. The issue wasn’t due to the depth of the components or the grid being a grid, but rather that the nested grid was calculating a layout with a height of 100%.

The new form grid components I built stretch vertically with a patterned background to fill the empty space. There are many ways to accomplish this, but the approach I went with sets the nested grid’s height to 100%. Unfortunately, this is an expensive calculation in Safari, but I didn’t realize it because I don’t spend much time in Safari, save for a once-over to make sure everything looks okay. As soon as I removed the 100% height, Safari became as lightning fast as the other browsers, but the nested grid no longer stretched to fill the empty space with the patterned background.

Now that I was able to pinpoint the culprit, I knew I could achieve the same visual I needed by simply moving the patterned background to the parent grid. I made the change, then verified all of the various grid layouts in Cushion. Everything looked as expected. More importantly, everything operated in a performant manner across all browsers. I was finally able close the book on this tiny, mysterious performance issue that caused so much additional stress amidst an already stressful time. While the good samaritan in me should take the time to create a repo with a reproducible example of this Safari bug and open a Webkit ticket, I think I just need a break from it.