<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vinay Reddy</title>
    <description>The latest articles on DEV Community by Vinay Reddy (@vinay_reddy_b22f3bf1f2748).</description>
    <link>https://dev.to/vinay_reddy_b22f3bf1f2748</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3889332%2F488f74d5-48f8-4c7b-a425-15ba21f91ed7.png</url>
      <title>DEV Community: Vinay Reddy</title>
      <link>https://dev.to/vinay_reddy_b22f3bf1f2748</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vinay_reddy_b22f3bf1f2748"/>
    <language>en</language>
    <item>
      <title>Fixing a UI State Bug in Joplin: Preventing Tag Operations on Deleted Notes</title>
      <dc:creator>Vinay Reddy</dc:creator>
      <pubDate>Sun, 02 Aug 2026 13:32:15 +0000</pubDate>
      <link>https://dev.to/vinay_reddy_b22f3bf1f2748/from-bug-report-to-merged-pr-fixing-a-ui-state-bug-in-joplin-34ia</link>
      <guid>https://dev.to/vinay_reddy_b22f3bf1f2748/from-bug-report-to-merged-pr-fixing-a-ui-state-bug-in-joplin-34ia</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Overview
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://joplinapp.org/" rel="noopener noreferrer"&gt;Joplin&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h1&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;While testing different note-management workflows, I noticed something unexpected.&lt;/p&gt;

&lt;p&gt;After deleting a note, the &lt;strong&gt;"Click to add tags..."&lt;/strong&gt; action in the status bar was still active and clickable.&lt;/p&gt;

&lt;p&gt;Even though the selected note no longer existed, the interface still behaved as though tag operations were available.&lt;/p&gt;

&lt;p&gt;From a user's perspective, this was confusing because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The note had already been deleted.&lt;/li&gt;
&lt;li&gt;The UI continued to expose tag-related actions.&lt;/li&gt;
&lt;li&gt;Clicking the action still attempted to execute the tag command.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Expected Behavior
&lt;/h3&gt;

&lt;p&gt;After deleting a note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag operations should immediately become unavailable.&lt;/li&gt;
&lt;li&gt;The UI should clearly indicate that the action is disabled.&lt;/li&gt;
&lt;li&gt;No tag-related commands should be executed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Root Cause
&lt;/h2&gt;

&lt;p&gt;While investigating the component, I found that the application already knew whether tag operations should be enabled through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTagsToolbarButtonInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the clickable element displayed in the status bar ignored this state.&lt;/p&gt;

&lt;p&gt;As a result, clicking the control still executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;CommandService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setTags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;noteIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even when the selected note had already been deleted.&lt;/p&gt;

&lt;p&gt;The underlying application state was correct, but the UI wasn't respecting it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Code
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Issue:&lt;/strong&gt; &lt;a href="https://github.com/laurent22/joplin/issues/15247" rel="noopener noreferrer"&gt;https://github.com/laurent22/joplin/issues/15247&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Request:&lt;/strong&gt; &lt;a href="https://github.com/laurent22/joplin/pull/15253" rel="noopener noreferrer"&gt;https://github.com/laurent22/joplin/pull/15253&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Core Fix
&lt;/h3&gt;

&lt;p&gt;Instead of always executing the command, the click handler now checks whether tag operations are currently enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;
    &lt;span class="na"&gt;aria-disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTagsToolbarButtonInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTagsToolbarButtonInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;CommandService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setTags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;noteIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To improve the user experience, I also updated the component styling so users immediately understand that the action is unavailable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
    &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTagsToolbarButtonInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTagsToolbarButtonInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pointer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  My Improvements
&lt;/h1&gt;

&lt;p&gt;This contribution focused on improving both application reliability and the overall user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevented Invalid Operations
&lt;/h2&gt;

&lt;p&gt;The click handler now exits immediately whenever tag operations are disabled, preventing invalid commands from being executed on deleted notes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improved Accessibility
&lt;/h2&gt;

&lt;p&gt;I added the &lt;code&gt;aria-disabled&lt;/code&gt; attribute so assistive technologies can correctly identify the disabled state of the control.&lt;/p&gt;

&lt;p&gt;This makes the interface more accessible while also reflecting the component's true state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Better Visual Feedback
&lt;/h2&gt;

&lt;p&gt;Previously, the tag action always appeared interactive.&lt;/p&gt;

&lt;p&gt;Now the control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reduces its opacity,&lt;/li&gt;
&lt;li&gt;changes the cursor,&lt;/li&gt;
&lt;li&gt;clearly communicates that the action is unavailable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps users immediately understand why they cannot perform the operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improved State Consistency
&lt;/h2&gt;

&lt;p&gt;One of the biggest goals of this fix was keeping the UI synchronized with the application's internal state.&lt;/p&gt;

&lt;p&gt;Once a note has been deleted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the interface no longer exposes invalid actions,&lt;/li&gt;
&lt;li&gt;commands are no longer executed,&lt;/li&gt;
&lt;li&gt;users receive accurate visual feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the application feel more predictable and prevents confusing interactions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing
&lt;/h1&gt;

&lt;p&gt;After implementing the fix, I verified several workflows to ensure normal functionality was preserved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a note.&lt;/li&gt;
&lt;li&gt;Add tags successfully.&lt;/li&gt;
&lt;li&gt;Verify normal behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scenario 2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Delete the note.&lt;/li&gt;
&lt;li&gt;Attempt to click &lt;strong&gt;"Click to add tags..."&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Confirm that the command is not executed.&lt;/li&gt;
&lt;li&gt;Verify that the control appears disabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Regression Testing
&lt;/h3&gt;

&lt;p&gt;I also confirmed that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing notes still allow normal tag management.&lt;/li&gt;
&lt;li&gt;No regressions were introduced for standard note workflows.&lt;/li&gt;
&lt;li&gt;The disabled state is correctly reflected immediately after deletion.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Results
&lt;/h1&gt;

&lt;p&gt;After this change:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;p&gt;This contribution reinforced an important engineering lesson:&lt;/p&gt;

&lt;p&gt;Not every impactful contribution is a large feature.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Contributing to Joplin was a rewarding experience, and this bug fix reminded me why I enjoy contributing to open source.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;This contribution reinforced a few important software engineering principles:&lt;/p&gt;

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

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

&lt;p&gt;Happy debugging! &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>opensource</category>
      <category>joplin</category>
    </item>
  </channel>
</rss>
