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
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);
even when the selected note had already been deleted.
The underlying application state was correct, but the UI wasn't respecting it.
Code
- GitHub Issue: https://github.com/laurent22/joplin/issues/15247
- Pull Request: https://github.com/laurent22/joplin/pull/15253
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);
}}
>
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",
}}
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)