shiftgen
recreationalcoder

Case Study: ShiftGen

ShiftGen

This ER admin staff scheduling company wanted their popular app brought into the modern era! With the help of visual designer Logan LaBo, we designed and implemented a sleek new interface!

The client's goal was to preserve the functionality while modernizing the application and improving user workflows on the frontend where possible without backend changes. We approached this challenge with the following plan:

  1. Analyze the various pages/views of the application and look for areas of redundancy, anti-patterns, and low-hanging fruit.
  2. Use the insights from the analysis to create an initial design system and component library to capture the functionality and propose improvements.
  3. Rearchitect the frontend tech stack to take advantage of the design system and component library.
  4. Implement the solution brick by brick, building on the main patterns as quickly as possible to amplify the benefits.

Throughout the process, with help from my visual design buddy Logan, we were able to formalize design concepts and gather feedback from the client and iterate where needed. There was a lot of creative freedom, and we leveraged that to tackle some key obstacles.

UI/UX Obstacles

One of the views critical to the application is the Schedule Modification view. This administrative view displays a Schedule consisting of individual Shifts on each day, each with an assignee, and enables various administrative actions.

Shift Calendar

The Shift Calendar ended up being the most complicated and feature-rich component due to the fact that a lot of the views presented information this way, and we wanted a consistent experience for the user. In the existing UI, the Shift Calendar is presented as a table where each cell is a Shift and admins can take actions on a Shift by clicking on the cell and then selecting an action.

Old Calendar

In the new design, we replace the tabular display with a set of collapsible containers for each week, and allow hover interactions to expand the Shift actions. This creates a more dynamic space for the user to work in and gives us a more modular visual framework to use in the other views where the Shift Calendar is presented with different feature sets.

New Calendar
New Calendar in Another View

Shift Highlight

As an admin audits the Schedule and makes assignment changes, they need a way to visualize which Shifts a worker is assigned, when workers are unavailable, and even if they have put in a request to work a Shift. The Shift Highlight feature makes this possible with Highlighter indicators, however the old solution presented the user with potentially crowded visual information, leading to increased cognitive load. The information would be compounded when multiple workers were highlighted, which made it more difficult for an admin to work effectively with the view.

Old Highlight Feature

In the new design, there is a more subtle approach and consistent visual treatment between the primary and secondary highlighted workers to minimize the cognitive load for the admin. This approach allows them to understand worker status at a glance and get more information deliberately by focusing on a particular shift.

New Highlight Feature

Shift Filtering

Shifts are organized into Shift Groups which can be used to filter the displayed Shifts. The existing design uses a simple and rigid checkbox-filled table to enable filtering.

Old Filter Feature

We were able to improve the UX of the Shift filtering feature by introducing the concept of dynamic MultiToken Selects inside of a popover to replace the existing expansive and rugged design.

New Filter Feature
UI/UX Obstacles

Tech Stack and Development

The server application was originally built in 2013 using Ruby on Rails and largely maintained on the original versions of Ruby 1.8 and Rails 2.3. The existing frontend was largely built using vanilla HTML/CSS and JavaScript with jQuery. For the new frontend, we proposed introducing some frameworks that would make development more efficient and save my client money in the long run. We arrived at the following stack after researching frameworks that would be more compatible with legacy Ruby on Rails projects and minimize friction with the existing development team's experience:

  1. ViewComponent for the layouts (RoR+HTML)
  2. Stimulus for the interactivity (JS)
  3. Tailwind for the visual theme and styles (CSS)

In order to use some of these frameworks though, we needed to upgrade the backend several major versions. We intially implemented UI Components in a sandbox in parallel with ShiftGen's backend engineers until the minimum requirements for the new frontend were met. We periodically helped with debugging and gave advice on prioritization of issues based on what was in scope for the frontend changes. Once the backend stack upgrade was completed, we ported the Component code into the existing application, which presented some new challenges involving the new Rails asset pipeline.

Now that we had a basic set of components integrated into the application, the refactoring began. The basic refactoring pattern was to use modular SSR ViewComponents styled with Tailwind and inject any necessary interactivity using Stimulus. Due to the high degree of coupling between the frontend and backend in the legacy code, refactoring several times was often necessary. We eventually found decent, reusable paradigms to replace all sorts of existing legacy micropatterns: CSRF token injection, form submission actions, and even interpretation of vanilla JS server responses.

Ultimately, after logging over 350 development hours, we touched more than 300 files and wrote over 11,000 lines of code to complete the project.

Tech Stack and Development

Optimization

When this application was originally written, it was done incredibly simply, which minimized its client-side footprint. One of the notable changes after refactoring and implementing the new designs is the fact that the sheer scale of the size of the client-side HTML and JS code is an order of magnitude higher than before. There is significantly more rendered HTML code due to the use of ViewComponent and Tailwind (more complex layouts, repeated class names, etc.) and quite a bit more interactive and dynamic code execution due to the improved UI/UX decisions.

With a modern computer and browser, the average Schedule rendered and performed just fine in initial testing. However, it became clear that the outlier Schedules with more than a few hundred Shifts were slow to load. Load time was also a major issue on the All Site View, which presented data from multiple Schedules all on the same page. And so, we put on our code performance hard hat and started to dig.

Chain of Thought

Let's define Load Time as the time it takes to enable smooth user interactivity with the UI. This is a very important metric to optimize, as it directly affects the user experience and can be a major source of frustration.

Strategy: Use performance monitoring to identify Load Time bottlenecks

By observing load times for different schedules, we can infer performance scales roughly linearly with the number of ShiftCell components.

We see that there are two parts to the delay:

  • Server response time is significantly increased compared to the previous implementation (~175MB vs ~3MB).
  • We can also see that there is a significant delay in rendering and allowing user interaction on the client after the response was received, but we cannot yet quantify this.

We cannot dig deeper yet with the current implementation, as it is impossible to complete a Performance monitoring run in Chrome dev tools. From this we can infer that there is a significant CPU draw on the client after the server response.

Tactic: Reduce the response size to see if that was causing any or all the time delays

Initially, the Tailwind classes seemed to be quite verbose, so steps were taken to move lower-level component Tailwind references into the component-specific Tailwind @layer. This reduced the response size by ~20% but did not seem to make a huge difference in the load time.

Downloading the page's HTML and analyzing node sizes, we see that the Options component HTML for all the Availability dropdowns in each ShiftCell totaled about 100MB. Removing these from the response reduced the response time by 40% and reduced CPU usage enough that Performance monitoring was able to be completed, but notably did not reduce the render time.

We also noted that the original implementation only included Availability on shifts assigned to the logged-in user, so we could actually make these render conditionally. This change alone is already a huge improvement.

Tactic: Optimize the rendering time through client-side performance analysis

After reviewing some Performance monitoring runs, we see the render time is largely comprised of querySelectorAll calls. With some research, we find that those calls were being made by the Stimulus framework in order to register ShiftCell outlets for the ShiftCalendar controller when it is initialized. Since these outlets are not needed during the initialization period, we use another mechanism to reference the ShiftCell controllers dynamically (for filtering/highlighting). We also take a final step to avoid applying filters when they were initialized as it was a no-op.

The new mechanism is significantly more efficient and reduced render times to just a few seconds!

Optimization

Conclusion

This project was a great experience refactoring a large, legacy codebase while modernizing the tech stack and UI/UX of the application. It also came with unique UX challenges that were satisfying to address, and technical challenges that required thorough research and debugging skills. We were able to provide a holistic solution that improved both the UX as well as any further design and development roadmaps for the application, and the stakeholders of the project were quite pleased with the results!