DEV Community

Harshil Patel
Harshil Patel

Posted on

Stepping Out of My Comfort Zone: Refactoring Menus in ChatCraft

Hi! My name is Harshil, and I am currently enrolled in an Open Source Development course at Seneca College. I had heard great reviews about this course, which is why I decided to enroll. During the lectures, I remember my professor repeatedly saying to "go out of your comfort zone" while contributing to open-source repositories. At the time, I didn’t give much thought to this advice.

Last month, I contributed to four open-source repositories, and I just realized that I did those contributions while staying in my comfort zone. I was working with technologies I was familiar with, tackling issues I knew how to resolve, and handling tasks that weren’t particularly challenging for me. While these contributions were valuable, I now understand that I wasn’t learning much by staying in my comfort zone.

This blog is about an issue I contributed to that pushed me out of my comfort zone. It was during this experience that I realized learning happens when you push yourself beyond what feels comfortable. Growth truly begins when you step outside your comfort zone.


Issue

This blog is about my contribution to ChatCraft.

GitHub logo tarasglek / chatcraft.org

Developer-oriented ChatGPT clone

ChatCraft.org

Welcome to ChatCraft.org, your open-source web companion for coding with Large Language Models (LLMs). Designed with developers in mind, ChatCraft transforms the way you interact with GPT models, making it effortless to read, write, debug, and enhance your code.

Whether you're exploring new designs or learning about the latest technologies, ChatCraft is your go-to platform. With a user interface inspired by GitHub, and editable Markdown everywhere, you'll feel right at home from the get-go.

ChatCraft UI Example

Features

We think ChatCraft is the best platform for learning, experimenting, and getting creative with code. Here's a few of the reasons why we think you'll agree:

🛠️ You're in Control: Customize all aspects of a chat. Use your own System Prompts, edit, delete, and retry AI messages with models from competing vendors in the same chat.

🌍 Multiple AI Providers: ChatCraft supports both OpenAI and OpenRouter, giving you access to a…

The task for this assignment was to tackle bigger issues than the ones I worked on last month. While looking for issues, I found an interesting one in ChatCraft:

Re-use features from Ask menu in Retry sub-menu #643

In #642 @menghif did some expert work to make the Ask menu more responsive and easier to use. We have a similar need in the Retry menu for AI Message responses. It would be interesting to see if we can re-use the same UX there, such that you can switch providers/models when retrying.

The issue, "Re-use features from Ask menu in Retry sub-menu," focuses on improving the Retry menu for AI responses by reusing the better design and functionality from the Ask menu. Essentially, the task was to develop a reusable component for the menu that could be rendered in different places.

Since I had contributed to ChatCraft before, I already had a local clone of the repository. However, when I tried pulling the latest changes, I realized my local repository wasn’t up-to-date with the original repository. At that point, I needed to set up a remote upstream. After setting it up, I successfully pulled the latest changes and got started.


Contribution Process

I started by identifying the files I needed to change. Once I found them, I took time to understand the context and figured out the logic behind the menu functionality, which needed to be extracted into a reusable component. Essentially, there were two places—the Ask menu and the Retry menu—that rendered menu components, and the maintainer wanted to reuse the same logic in both places.

I wasn’t very familiar with the menu component from @chakra-ui/react, so I researched it and found that its structure looked like this:

Menu > MenuButton > MenuList > MenuGroup > MenuItem
Enter fullscreen mode Exit fullscreen mode

Initially, I thought about making MenuList reusable while keeping Menu and MenuButton at their original locations since the Ask menu and Retry menu had some differences in functionality, like hover effects and click handling. However, this approach didn’t work because the Ask menu used components from @chakra-ui/react, while the Retry menu used components from @szhsin/react-menu. This difference in libraries meant I had to make the entire menu reusable instead of just parts of it. I mentioned this in the issue's comments:

Comment for #643

Here's the corrected and improved version:


Hi @humphd, I tried to separate out the menu logic into a separate component, and it worked. Here is the demo:

Demo Link

I just need to confirm one thing. I used a conditional in the component to render the MenuButton like this:

{menuButtonLabel &&
      React.isValidElement(menuButtonLabel) &&
      menuButtonLabel.type === SubMenu ? (
        <MenuButton
          as={Box}
          fontSize="1rem"
          fontWeight="normal"
          cursor="pointer"
          color="inherit"
          padding="0"
          background="none"
          border="none"
          aria-label={label || "Choose Model"}
          title={label || "Choose Model"}
          onClick={openOnHover ? undefined : onOpen} // Open on click if not openOnHover
          onMouseEnter={openOnHover ? onOpen : undefined} // Open on hover if openOnHover
          onMouseLeave={openOnHover ? onClose : undefined} // Close on hover out if openOnHover
        >
          {menuButtonLabel}
        </MenuButton>
      ) : (
        <MenuButton
          as={IconButton}
          size="sm"
          fontSize="1.25rem"
          aria-label={label || "Choose Model"}
          title={label || "Choose Model"}
          icon={<TbChevronUp />}
          onClick={openOnHover ? undefined : onOpen} // Open on click if not openOnHover
          onMouseEnter={openOnHover ? onOpen : undefined} // Open on hover if openOnHover
          onMouseLeave={openOnHover ? onClose : undefined} // Close on hover out if openOnHover
          borderLeftRadius="0"
        />
      )}
Enter fullscreen mode Exit fullscreen mode

The reason for this approach is that both use cases require MenuButton to function differently. Initially, I tried to keep this logic separate and only shared the menuItem logic, but it didn’t work. This is because the two places where this component needs to render use different menu libraries: one uses @chakra-ui/react components, and the other uses @szhsin/react-menu. That’s why I had to make the entire Menu component reusable.

What do you think?

To handle the differences, I used an ad-hoc approach for rendering MenuButton. The Ask menu required a click effect to open, while the Retry menu used a hover effect. With this solution in place, everything was working as expected, so I created a pull request (PR):

Added ModelProviderMenu component and reused it in PromptsendButton and MessageBase #729

This fixes #643.

Added a reusable component, ModelProviderMenu, and replaced the logic in PromptSendButton and MessageBase to utilize this component.

Overview of Changes:

  1. Added a new component:
    ModelProviderMenu contains the logic for displaying models and providers, allowing the user to change models and providers directly from it.

  2. Updated PromptSendButton:
    Replaced the existing menu logic in PromptSendButton with the ModelProviderMenu component. This menu appears next to the send button.

  3. Updated MessageBase:
    Refactored the retry logic in MessageBase to use the ModelProviderMenu component, ensuring consistency and reusability.

Demo

https://github.com/user-attachments/assets/b55bf561-08c3-4d74-8958-1f7b3c6ac559

Interestingly, the owner added me as a developer for the repository and asked me to send another PR from the original repository’s branch.

Image description

I accepted the developer request and cloned the original repository. Initially, I was confused about how to transfer my changes from my forked repository’s branch to the original repository’s branch. Then I remembered I could set up my forked repository as a remote, fetch the branches, and merge the changes. I did that successfully and made another PR from the original repository branch.


Change Requests

After making the PR, the maintainer started reviewing my changes and requested several updates—eight change requests in total.

Added ModelProviderMenu component and reused it in PromptsendButton and MessageBase #730

This is Requested from #729

</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/tarasglek/chatcraft.org/pull/730">View on GitHub</a></div>
Enter fullscreen mode Exit fullscreen mode


It turned out that my ad-hoc approach was making the new component less reusable and introduced bugs. The maintainer suggested that the new component should use @szhsin/react-menu instead of @chakra-ui/react, and asked me to revisit the previous approach of extracting MenuList.

I started by making the required changes and created a new component called ModelSelectionMenuList, extracting the MenuList into this component. It worked as expected for the Retry menu, as its parent component was already using @szhsin/react-menu. However, it failed for the Ask menu, which was still using @chakra-ui/react components.

I reached out to the maintainer again, and they advised refactoring the Ask menu to also use @szhsin/react-menu components. Following their guidance, I successfully refactored the Ask menu to align with the new structure. This made the component consistent and reusable across both menus.


Mobile Screen Refactoring

While refactoring the Ask menu, I discovered that it was rendered differently for mobile and laptop screens. To address this, I updated my new component to handle both variations by using the same template logic to render different menus based on screen size. I also ensured that the Ask menu for mobile screens was refactored appropriately.


Final Thoughts

In total, I spent around two full days completing the PR for this issue. During the process, I had moments of doubt, feeling like I might not be able to finish it. However, I persevered and completed the task, gaining valuable knowledge in the process.

The most important lesson I learned from this issue—and from the Open Source Development course—is to challenge myself and step out of my comfort zone. Growth truly happens when we push our boundaries, and this experience reinforced that belief.

Top comments (0)