DEV Community

Cover image for Fixing a UI State Bug in Joplin: Preventing Tag Operations on Deleted Notes
Vinay Reddy
Vinay Reddy

Posted on

Fixing a UI State Bug in Joplin: Preventing Tag Operations on Deleted Notes

Summer Bug Smash: Clear the Lineup 🐛🛹

Open source isn't only about building new features—it’s also about making software more reliable by fixing the small issues that affect the user experience. While contributing to Joplin, I encountered a subtle UI state bug that allowed users to interact with tag operations even after a note had been deleted.

Although the application didn't crash, the interface no longer reflected the true state of the application. Here's how I investigated the issue and implemented the fix.


Project Overview

Joplin is a popular open-source note-taking application that supports Markdown, notebooks, tags, attachments, synchronization, and end-to-end encryption across desktop and mobile platforms.

While exploring the desktop application and contributing to the project, I discovered a state-management issue involving deleted notes and tag operations. The problem wasn't immediately obvious, but it created an inconsistent user experience by exposing actions that should no longer have been available.

This contribution focuses on improving the reliability and consistency of the Joplin desktop application by ensuring that users cannot perform tag operations on notes that have already been deleted.


Bug Fix or Performance Improvement

The Bug

While testing different note-management workflows, I noticed something unexpected.

After deleting a note, the "Click to add tags..." action in the status bar was still active and clickable.

Even though the selected note no longer existed, the interface still behaved as though tag operations were available.

From a user's perspective, this was confusing because:

  • The note had already been deleted.
  • The UI continued to expose tag-related actions.
  • Clicking the action still attempted to execute the tag command.

Although this didn't result in a crash, it created an inconsistent application state and allowed interactions that should never have been possible.

Expected Behavior

After deleting a note:

  • Tag operations should immediately become unavailable.
  • The UI should clearly indicate that the action is disabled.
  • No tag-related commands should be executed.

Root Cause

While investigating the component, I found that the application already knew whether tag operations should be enabled through:

props.setTagsToolbarButtonInfo.enabled
Enter fullscreen mode Exit fullscreen mode

However, the clickable element displayed in the status bar ignored this state.

As a result, clicking the control still executed:

CommandService.instance().execute("setTags", noteIds);
Enter fullscreen mode Exit fullscreen mode

even when the selected note had already been deleted.

The underlying application state was correct, but the UI wasn't respecting it.


Code

Core Fix

Instead of always executing the command, the click handler now checks whether tag operations are currently enabled.

<span
    aria-disabled={!props.setTagsToolbarButtonInfo.enabled}
    onClick={(e) => {
        e.preventDefault();
        e.stopPropagation();

        if (!props.setTagsToolbarButtonInfo.enabled) return;

        void CommandService.instance().execute("setTags", noteIds);
    }}
>
Enter fullscreen mode Exit fullscreen mode

To improve the user experience, I also updated the component styling so users immediately understand that the action is unavailable.

style={{
    opacity: props.setTagsToolbarButtonInfo.enabled ? 1 : 0.5,
    cursor: props.setTagsToolbarButtonInfo.enabled
        ? "pointer"
        : "default",
}}
Enter fullscreen mode Exit fullscreen mode

My Improvements

This contribution focused on improving both application reliability and the overall user experience.

Prevented Invalid Operations

The click handler now exits immediately whenever tag operations are disabled, preventing invalid commands from being executed on deleted notes.


Improved Accessibility

I added the aria-disabled attribute so assistive technologies can correctly identify the disabled state of the control.

This makes the interface more accessible while also reflecting the component's true state.


Better Visual Feedback

Previously, the tag action always appeared interactive.

Now the control:

  • reduces its opacity,
  • changes the cursor,
  • clearly communicates that the action is unavailable.

This helps users immediately understand why they cannot perform the operation.


Improved State Consistency

One of the biggest goals of this fix was keeping the UI synchronized with the application's internal state.

Once a note has been deleted:

  • the interface no longer exposes invalid actions,
  • commands are no longer executed,
  • users receive accurate visual feedback.

This makes the application feel more predictable and prevents confusing interactions.


Testing

After implementing the fix, I verified several workflows to ensure normal functionality was preserved.

Scenario 1

  • Create a note.
  • Add tags successfully.
  • Verify normal behavior.

Scenario 2

  • Delete the note.
  • Attempt to click "Click to add tags...".
  • Confirm that the command is not executed.
  • Verify that the control appears disabled.

Regression Testing

I also confirmed that:

  • Existing notes still allow normal tag management.
  • No regressions were introduced for standard note workflows.
  • The disabled state is correctly reflected immediately after deletion.

Results

After this change:

  • Tag operations are disabled immediately after deleting a note.
  • Invalid commands are prevented before execution.
  • Users receive clear visual feedback.
  • The interface stays synchronized with the application's internal state.
  • The overall desktop experience becomes more predictable and reliable.

The pull request was reviewed by the Joplin maintainers, accepted, and merged into the project, making the fix available to the wider open-source community.


What I Learned

This contribution reinforced an important engineering lesson:

Not every impactful contribution is a large feature.

Sometimes, improving software means identifying subtle inconsistencies between the UI and the application's state, understanding why they occur, and ensuring users can only perform valid actions.

Working through this issue also gave me a deeper understanding of React event handling, UI state management, accessibility, and the importance of keeping frontend behavior aligned with backend logic.


Final Thoughts

Contributing to Joplin was a rewarding experience, and this bug fix reminded me why I enjoy contributing to open source.

Small improvements can have a meaningful impact on the experience of thousands of users. Beyond writing code, I learned the value of careful debugging, understanding the root cause of an issue, and implementing a solution that is both technically correct and user-friendly.

A big thank you to the Joplin maintainers for their guidance, code review, and for maintaining such a welcoming open-source community.

Lessons Learned

This contribution reinforced a few important software engineering principles:

  • UI components should always reflect the application's actual state.
  • Disabling invalid actions is better than allowing them to fail later.
  • Small usability fixes can have a meaningful impact on the overall user experience.
  • Accessibility improvements, such as aria-disabled, can be incorporated naturally while fixing functional bugs.

Even though this was a relatively small change, it highlighted how thoughtful debugging and attention to detail contribute to building more reliable software.

Happy debugging!

Top comments (0)