<?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>Forem: Rolan Lobo</title>
    <description>The latest articles on Forem by Rolan Lobo (@rolan_r_n_r).</description>
    <link>https://forem.com/rolan_r_n_r</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3551696%2F5fb97d3c-ee7d-42c8-9da4-4122b6e46ab0.jpg</url>
      <title>Forem: Rolan Lobo</title>
      <link>https://forem.com/rolan_r_n_r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rolan_r_n_r"/>
    <language>en</language>
    <item>
      <title>🔐 AES-256 Finally Makes Sense (And It’s Way Simpler Than You Think)</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:11:12 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/aes-256-finally-makes-sense-and-its-way-simpler-than-you-think-3kmc</link>
      <guid>https://forem.com/rolan_r_n_r/aes-256-finally-makes-sense-and-its-way-simpler-than-you-think-3kmc</guid>
      <description>&lt;p&gt;Let’s be honest…&lt;/p&gt;

&lt;p&gt;We’ve all heard this everywhere:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Your data is secured with AES-256 encryption”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cool… but &lt;strong&gt;what does that even mean?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used to just nod like:&lt;br&gt;
“Yeah yeah… sounds secure 😌”&lt;/p&gt;

&lt;p&gt;But recently, I &lt;em&gt;actually&lt;/em&gt; understood it — and trust me, it’s not as scary as it sounds.&lt;/p&gt;

&lt;p&gt;So let me explain it to you in the simplest (and fun) way possible 👇&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 First, What is Encryption?
&lt;/h2&gt;

&lt;p&gt;Encryption is basically:&lt;/p&gt;

&lt;p&gt;👉 Turning your readable data into &lt;strong&gt;gibberish&lt;/strong&gt; so no one else can understand it.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Hello → X7#pL@9!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only someone with the &lt;strong&gt;correct key&lt;/strong&gt; can turn it back into "Hello".&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 So What is AES?
&lt;/h2&gt;

&lt;p&gt;AES stands for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Encryption Standard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s one of the most secure encryption methods used today.&lt;/p&gt;

&lt;p&gt;Used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Banking systems 💳
&lt;/li&gt;
&lt;li&gt;WhatsApp messages 💬
&lt;/li&gt;
&lt;li&gt;Wi-Fi passwords 📶
&lt;/li&gt;
&lt;li&gt;Even governments 🏛️
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yeah… it’s &lt;em&gt;that serious.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💪 What Does “256” Mean?
&lt;/h2&gt;

&lt;p&gt;AES comes in 3 types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES-128
&lt;/li&gt;
&lt;li&gt;AES-192
&lt;/li&gt;
&lt;li&gt;AES-256
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 The number = &lt;strong&gt;key size (in bits)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So AES-256 = &lt;strong&gt;256-bit key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which basically means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔐 There are 2^256 possible keys&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That number is insanely huge.&lt;/p&gt;

&lt;p&gt;Like… even if you had the fastest computer in the world,&lt;br&gt;
it would take &lt;strong&gt;billions of years&lt;/strong&gt; to brute-force it.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 Imagine This (Simple Analogy)
&lt;/h2&gt;

&lt;p&gt;Think of AES-256 like this:&lt;/p&gt;

&lt;p&gt;You have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;super strong locker&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;256-digit password&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;And inside it → your data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine someone trying to guess that password…&lt;/p&gt;

&lt;p&gt;Yeah… not happening anytime soon 😂&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ How AES-256 Actually Works (Simple Version)
&lt;/h2&gt;

&lt;p&gt;Okay, no boring math — just the idea:&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Data is split into blocks
&lt;/h3&gt;

&lt;p&gt;Your data is divided into chunks (16 bytes each)&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 2: Multiple rounds of scrambling (14 rounds!)
&lt;/h3&gt;

&lt;p&gt;Each round does things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixing data&lt;/li&gt;
&lt;li&gt;Shuffling bits&lt;/li&gt;
&lt;li&gt;Substituting values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically:&lt;br&gt;
👉 It completely &lt;strong&gt;scrambles your data again and again&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 3: Secret key is applied
&lt;/h3&gt;

&lt;p&gt;A 256-bit key is used in every round&lt;/p&gt;

&lt;p&gt;This is what makes it secure 🔥&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 4: Final Output
&lt;/h3&gt;

&lt;p&gt;You get encrypted data like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
k9@Lm#2!zPq8...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completely unreadable.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔓 How Decryption Works
&lt;/h2&gt;

&lt;p&gt;To get the original data back:&lt;/p&gt;

&lt;p&gt;👉 You MUST have the same key&lt;/p&gt;

&lt;p&gt;Then the process runs &lt;strong&gt;in reverse&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And boom 💥&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
k9@Lm#2!zPq8... → Hello

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚫 Why Hackers Can’t Easily Break AES-256
&lt;/h2&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too many possible keys (2^256 🤯)&lt;/li&gt;
&lt;li&gt;Too many transformation rounds&lt;/li&gt;
&lt;li&gt;No shortcut to guess the correct key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even supercomputers struggle.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Where You See AES-256 in Real Life
&lt;/h2&gt;

&lt;p&gt;You’re already using it daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔐 HTTPS websites (the lock icon in browser)&lt;/li&gt;
&lt;li&gt;💬 Messaging apps&lt;/li&gt;
&lt;li&gt;💾 File encryption tools&lt;/li&gt;
&lt;li&gt;☁️ Cloud storage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧑‍💻 Why I Learned This
&lt;/h2&gt;

&lt;p&gt;While working on my project &lt;strong&gt;InvisioVault&lt;/strong&gt; (a file-hiding tool),&lt;br&gt;
I realized:&lt;/p&gt;

&lt;p&gt;👉 Hiding data is cool&lt;br&gt;&lt;br&gt;
👉 But securing it is &lt;em&gt;next level&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s when I explored AES-256.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✨ Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AES-256 sounds complicated…&lt;/p&gt;

&lt;p&gt;But in reality, it’s just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔐 A super powerful way to scramble data using a secret key&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re a developer,&lt;br&gt;
understanding this gives you a &lt;strong&gt;huge edge&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 If You Made It This Far…
&lt;/h2&gt;

&lt;p&gt;You officially understand AES-256 better than most beginners 💯&lt;/p&gt;

&lt;p&gt;If this helped you, drop a like ❤️ or share it!&lt;/p&gt;




&lt;h2&gt;
  
  
  👋 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;I’m a beginner dev building cool stuff and learning every day.&lt;/p&gt;

&lt;p&gt;More blogs coming soon 🚀&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🧨 The Bugs That Almost Broke Sortify (And How I Crushed Them)</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Sun, 15 Feb 2026 15:03:43 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/the-bugs-that-almost-broke-sortify-and-how-i-crushed-them-h3h</link>
      <guid>https://forem.com/rolan_r_n_r/the-bugs-that-almost-broke-sortify-and-how-i-crushed-them-h3h</guid>
      <description>&lt;p&gt;Let me tell you something.&lt;/p&gt;

&lt;p&gt;Apps don’t usually break because of complex algorithms.&lt;/p&gt;

&lt;p&gt;They break because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 0-byte file.&lt;/li&gt;
&lt;li&gt;A random binary blob pretending to be text.&lt;/li&gt;
&lt;li&gt;A filename like &lt;code&gt;report:final*version?.docx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Or an undo operation that travels back in time… badly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sortify ran into all of these.&lt;br&gt;
So I did what every developer eventually has to do:&lt;/p&gt;

&lt;p&gt;I went into &lt;strong&gt;defensive programming mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the story of how I hardened Sortify’s invalid input handling — and made it way more stable in the process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Check it out on GitHub:&lt;/strong&gt; &lt;br&gt;
🔗 &lt;a href="https://github.com/Mrtracker-new/Sortify" rel="noopener noreferrer"&gt;Sortify — automatic file organizer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🗂️ 1. The “Empty File” Problem
&lt;/h2&gt;

&lt;p&gt;You’d think an empty file wouldn’t be dangerous.&lt;/p&gt;

&lt;p&gt;It’s literally nothing.&lt;/p&gt;

&lt;p&gt;But that’s the problem.&lt;/p&gt;

&lt;p&gt;No content → extraction logic behaves weirdly → AI categorization becomes unreliable.&lt;/p&gt;

&lt;p&gt;So now we explicitly detect 0-byte files before doing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file_size_bytes&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Empty file detected (0 bytes): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What happens now?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Empty files are detected instantly&lt;/li&gt;
&lt;li&gt;✅ Logged properly&lt;/li&gt;
&lt;li&gt;✅ Categorized using filename only&lt;/li&gt;
&lt;li&gt;✅ No crashes&lt;/li&gt;
&lt;li&gt;✅ No silent weirdness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of “why did this behave strangely?”&lt;br&gt;
Now it’s “Ah, it’s empty. Makes sense.”&lt;/p&gt;

&lt;p&gt;Predictable &amp;gt; Magical.&lt;/p&gt;


&lt;h2&gt;
  
  
  💣 2. Binary Files Pretending to Be Text
&lt;/h2&gt;

&lt;p&gt;This one was sneaky.&lt;/p&gt;

&lt;p&gt;Some files looked harmless.&lt;/p&gt;

&lt;p&gt;But internally?&lt;/p&gt;

&lt;p&gt;Binary junk.&lt;/p&gt;

&lt;p&gt;And when text extraction libraries try to read binary data…&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exceptions&lt;/li&gt;
&lt;li&gt;Garbage output&lt;/li&gt;
&lt;li&gt;Or mysterious failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built a binary detector.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 How It Works
&lt;/h2&gt;

&lt;p&gt;We check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First 8KB of the file&lt;/li&gt;
&lt;li&gt;Presence of null bytes (&lt;code&gt;\x00&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Ratio of non-printable characters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If more than &lt;strong&gt;30% of characters are non-printable&lt;/strong&gt;, we treat it as binary.&lt;/p&gt;

&lt;p&gt;Why 30%?&lt;/p&gt;

&lt;p&gt;Because real text files don’t look like corrupted alien transmissions.&lt;/p&gt;


&lt;h3&gt;
  
  
  If file is binary:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Binary file detected, skipping content extraction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Skip text extraction&lt;/li&gt;
&lt;li&gt;✅ Fall back to filename-based categorization&lt;/li&gt;
&lt;li&gt;✅ Log everything&lt;/li&gt;
&lt;li&gt;✅ Avoid crashes completely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the file extension lies.&lt;/p&gt;

&lt;p&gt;Doesn’t matter if it’s &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt;, or &lt;code&gt;.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Binary is binary.&lt;/p&gt;

&lt;p&gt;No more surprise explosions.&lt;/p&gt;


&lt;h2&gt;
  
  
  😩 3. Windows Filename Rage
&lt;/h2&gt;

&lt;p&gt;Previously, if someone tried to rename a file using invalid Windows characters…&lt;/p&gt;

&lt;p&gt;They got a boring, unhelpful error.&lt;/p&gt;

&lt;p&gt;Now?&lt;/p&gt;

&lt;p&gt;We explain exactly what Windows hates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Windows does not allow these characters in filenames:
  \ (backslash)  / (forward slash)  : (colon)
  * (asterisk)   ? (question mark)  " (quote)
  &amp;lt; (less than)  &amp;gt; (greater than)  | (pipe)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please remove these characters and try again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simple change.&lt;/p&gt;

&lt;p&gt;Massive UX improvement.&lt;/p&gt;

&lt;p&gt;No more guessing what went wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  🕰️ 4. The Undo Operation That Could Have Been Dangerous
&lt;/h2&gt;

&lt;p&gt;Undo is supposed to feel magical.&lt;/p&gt;

&lt;p&gt;But under the hood, it’s risky.&lt;/p&gt;

&lt;p&gt;What if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The moved file was deleted?&lt;/li&gt;
&lt;li&gt;The original location now has a new file?&lt;/li&gt;
&lt;li&gt;Something changed between operations?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Previously, this could cause crashes or accidental overwrites.&lt;/p&gt;

&lt;p&gt;Now?&lt;/p&gt;

&lt;p&gt;We check everything.&lt;/p&gt;

&lt;p&gt;Before undoing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Does the moved file still exist?&lt;/li&gt;
&lt;li&gt;✅ Is the original location free?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not?&lt;/p&gt;

&lt;p&gt;We stop and clearly explain why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot undo: Original location is already occupied.
A file already exists at: ...
Please manually verify and move the file if needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No corruption.&lt;br&gt;
No overwriting.&lt;br&gt;
No chaos.&lt;/p&gt;

&lt;p&gt;This one was a critical-level fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Before vs After
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Empty files behaved weirdly&lt;/li&gt;
&lt;li&gt;Binary files could crash extraction&lt;/li&gt;
&lt;li&gt;Error messages were vague&lt;/li&gt;
&lt;li&gt;Undo could break in edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Empty files detected and logged&lt;/li&gt;
&lt;li&gt;Binary files safely skipped&lt;/li&gt;
&lt;li&gt;Clear, human-readable error messages&lt;/li&gt;
&lt;li&gt;Undo operations protected and safe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It feels like the app matured overnight.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 What This Really Is
&lt;/h2&gt;

&lt;p&gt;This wasn’t a “new feature” release.&lt;/p&gt;

&lt;p&gt;This was a &lt;strong&gt;stability hardening phase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The kind of work users never see.&lt;/p&gt;

&lt;p&gt;But they feel it.&lt;/p&gt;

&lt;p&gt;Because the app stops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crashing randomly&lt;/li&gt;
&lt;li&gt;Acting unpredictably&lt;/li&gt;
&lt;li&gt;Failing silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, it behaves like a professional desktop application.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Final Thought
&lt;/h2&gt;

&lt;p&gt;The difference between a prototype and a production-ready app?&lt;/p&gt;

&lt;p&gt;Not fancy AI.&lt;br&gt;
Not cool UI.&lt;/p&gt;

&lt;p&gt;It’s this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Handling weird, messy, real-world input gracefully.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Users will always find edge cases.&lt;/p&gt;

&lt;p&gt;Your job is to make sure they don’t break everything when they do.&lt;/p&gt;

&lt;p&gt;Sortify is now much harder to break.&lt;/p&gt;

&lt;p&gt;And honestly?&lt;/p&gt;

&lt;p&gt;That feels better than shipping a new feature. 💙&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>beginners</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fixing a Frozen UI &amp; a Sneaky Scheduler Crash — A Tale of Threads, Signals, and Defensive Code 🧵🔥</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Tue, 03 Feb 2026 14:43:07 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/fixing-a-frozen-ui-a-sneaky-scheduler-crash-a-tale-of-threads-signals-and-defensive-code-35gc</link>
      <guid>https://forem.com/rolan_r_n_r/fixing-a-frozen-ui-a-sneaky-scheduler-crash-a-tale-of-threads-signals-and-defensive-code-35gc</guid>
      <description>&lt;p&gt;If your app &lt;em&gt;looks&lt;/em&gt; alive but &lt;em&gt;feels&lt;/em&gt; dead… congratulations 🎉&lt;br&gt;&lt;br&gt;
You’ve probably blocked the main thread.&lt;/p&gt;

&lt;p&gt;This post is a walkthrough of two real bugs I fixed recently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A UI freeze caused by blocking file I/O on the main thread&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A nasty crash where a scheduler ran before it was initialized&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both bugs were invisible during “small tests” and brutally obvious in real usage.&lt;br&gt;&lt;br&gt;
Let’s break down what went wrong, how I fixed it, and what I learned.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧊 Problem #1: The UI Freeze from Hell
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clicking &lt;strong&gt;Organize Files&lt;/strong&gt; froze the entire UI&lt;/li&gt;
&lt;li&gt;Large file sets (50+ files) caused &lt;strong&gt;5+ seconds of silence&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No progress bar movement&lt;/li&gt;
&lt;li&gt;Users thought the app had crashed (fair assumption)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;I was doing &lt;em&gt;exactly&lt;/em&gt; what every GUI app should &lt;strong&gt;never&lt;/strong&gt; do:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Running file operations directly on the &lt;strong&gt;main GUI thread&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s the original code 😬&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Preview mode - BLOCKING code
&lt;/span&gt;&lt;span class="n"&gt;temp_file_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FileOperations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organized Files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;temp_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_operations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 🚨 This loop blocks the UI
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;category_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categorize_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;temp_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;temp_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize_operations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That innocent-looking &lt;code&gt;for&lt;/code&gt; loop?&lt;br&gt;
Yeah… that was freezing &lt;em&gt;everything&lt;/em&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✅ The Fix: Move Work Off the Main Thread
&lt;/h2&gt;

&lt;p&gt;Instead of doing file I/O directly, I refactored preview mode to use the &lt;strong&gt;same background threading system&lt;/strong&gt; already working for real file moves.&lt;/p&gt;
&lt;h3&gt;
  
  
  New Approach (Non-Blocking)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Preview mode - NON-BLOCKING code
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview_file_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FileOperations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organized Files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processing_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProcessingThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview_file_ops&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processing_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processing_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_on_preview_finished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processing_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_processing_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processing_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Why This Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;File processing runs in a &lt;strong&gt;background thread&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;UI stays responsive&lt;/li&gt;
&lt;li&gt;Progress bar updates in real time&lt;/li&gt;
&lt;li&gt;Errors are handled safely via signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✨ Chef’s kiss.&lt;/p&gt;


&lt;h2&gt;
  
  
  🪄 Showing the Preview &lt;em&gt;After&lt;/em&gt; the Work Is Done
&lt;/h2&gt;

&lt;p&gt;Instead of showing the preview dialog immediately, I added a new handler that fires &lt;strong&gt;only after the background thread finishes&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_preview_finished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest_dir&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;progress_bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dry_run_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_operations&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PreviewDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview_file_ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dry_run_manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;QDialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DialogCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_execute_organization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Preview cancelled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No frozen UI&lt;/li&gt;
&lt;li&gt;No half-ready preview&lt;/li&gt;
&lt;li&gt;Clean, predictable flow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Before vs After (Quick Visual)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Before (Blocking)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main Thread
 └── Loop over files
     └── UI freezes 😵
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ After (Non-Blocking)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main Thread        Background Thread
 ├── UI alive 🟢    └── File processing
 ├── Progress bar  └── Categorization
 └── Signals       └── Dry-run tracking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧨 Problem #2: The Scheduler That Crashed on Startup
&lt;/h2&gt;

&lt;p&gt;This one was sneakier.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Crash
&lt;/h3&gt;

&lt;p&gt;Sometimes the app would crash when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-sort was enabled before manual organization&lt;/li&gt;
&lt;li&gt;Scheduled jobs ran on fresh app start&lt;/li&gt;
&lt;li&gt;The app reopened with existing schedules&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Culprit
&lt;/h3&gt;

&lt;p&gt;The scheduler was created like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SortScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categorizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;None&lt;/code&gt;?&lt;br&gt;
Yeah… &lt;code&gt;file_ops&lt;/code&gt; wasn’t initialized yet.&lt;/p&gt;

&lt;p&gt;So when the scheduler tried to move files:&lt;/p&gt;

&lt;p&gt;💥 &lt;strong&gt;NoneType crash&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🛡️ The Fix: Lazy Initialization + Defensive Guards
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Don’t Initialize Too Early
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Delay scheduler creation
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2: Centralize Setup with &lt;code&gt;_ensure_file_ops()&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SortScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categorizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_ops&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;ul&gt;
&lt;li&gt;Scheduler only exists when &lt;code&gt;file_ops&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;No duplicated setup logic&lt;/li&gt;
&lt;li&gt;No race conditions&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧯 Step 3: Defense in Depth (Crash Prevention)
&lt;/h2&gt;

&lt;p&gt;Even with good initialization, I added &lt;strong&gt;guards&lt;/strong&gt; inside the scheduler and watcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_ops&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FileOperations not initialized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No crashes&lt;/li&gt;
&lt;li&gt;Errors are logged&lt;/li&gt;
&lt;li&gt;App degrades gracefully instead of exploding&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Final Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;❌ Frozen UI&lt;br&gt;
❌ No progress feedback&lt;br&gt;
❌ Random crashes&lt;br&gt;
❌ Users panic&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;p&gt;✅ Fully responsive UI&lt;br&gt;
✅ Smooth progress updates&lt;br&gt;
✅ Safe background execution&lt;br&gt;
✅ Stable scheduler &amp;amp; watcher&lt;br&gt;
✅ App feels &lt;em&gt;professional&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Testing Still Matters
&lt;/h2&gt;

&lt;p&gt;I manually tested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small file sets&lt;/li&gt;
&lt;li&gt;Large file sets&lt;/li&gt;
&lt;li&gt;Preview ON / OFF&lt;/li&gt;
&lt;li&gt;Auto-sort before manual use&lt;/li&gt;
&lt;li&gt;Scheduled jobs on restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything behaved exactly as expected 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never block the GUI thread&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Reuse proven threading patterns&lt;/li&gt;
&lt;li&gt;Lazy initialization beats eager crashes&lt;/li&gt;
&lt;li&gt;Defensive checks save your future self&lt;/li&gt;
&lt;li&gt;If the UI freezes, users will assume the worst&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you’re building desktop apps with Python + Qt:&lt;br&gt;
&lt;strong&gt;Threads + signals are not optional — they’re survival tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy coding 🧑‍💻🔥&lt;br&gt;
And may your UI never freeze again.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>productivity</category>
      <category>performance</category>
    </item>
    <item>
      <title>I Eliminated SQLite Race Conditions in a Multi-Threaded Python App 🚀</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Sun, 01 Feb 2026 14:59:13 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/i-eliminated-sqlite-race-conditions-in-a-multi-threaded-python-app-4eod</link>
      <guid>https://forem.com/rolan_r_n_r/i-eliminated-sqlite-race-conditions-in-a-multi-threaded-python-app-4eod</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Random crashes. Database corruption. “database is locked” errors.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;That’s how my app &lt;strong&gt;Sortify&lt;/strong&gt; behaved when multiple threads hit SQLite at the same time.  &lt;/p&gt;

&lt;p&gt;This post is how I &lt;strong&gt;fixed it properly&lt;/strong&gt; — and made the database production-ready.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 The Problem: SQLite + Threads = Trouble
&lt;/h2&gt;

&lt;p&gt;SQLite is lightweight and fast — but it has a &lt;strong&gt;big footgun&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ &lt;strong&gt;A single database connection shared across threads is NOT safe&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my app &lt;strong&gt;Sortify&lt;/strong&gt;, multiple components were running concurrently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-sort watcher&lt;/li&gt;
&lt;li&gt;Manual file operations&lt;/li&gt;
&lt;li&gt;Scheduler tasks&lt;/li&gt;
&lt;li&gt;Background processing threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them were touching the &lt;strong&gt;same SQLite connection&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms I Saw
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Random crashes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;database is locked&lt;/code&gt; errors&lt;/li&gt;
&lt;li&gt;Inconsistent history data&lt;/li&gt;
&lt;li&gt;Risk of database corruption&lt;/li&gt;
&lt;li&gt;App instability during concurrent operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This line was the silent killer 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_same_thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It &lt;em&gt;disables safety&lt;/em&gt;, but &lt;strong&gt;does not make SQLite thread-safe&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 Why This Happens
&lt;/h2&gt;

&lt;p&gt;SQLite &lt;strong&gt;allows multiple connections&lt;/strong&gt;, but &lt;strong&gt;each connection must stay in one thread&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ cursors&lt;/li&gt;
&lt;li&gt;❌ connections&lt;/li&gt;
&lt;li&gt;❌ transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;across threads causes &lt;strong&gt;race conditions&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ The Solution: Thread-Local Database Manager
&lt;/h2&gt;

&lt;p&gt;I implemented a &lt;strong&gt;proper thread-safe architecture&lt;/strong&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;threading.local()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Per-thread SQLite connections&lt;/li&gt;
&lt;li&gt;Automatic retry logic&lt;/li&gt;
&lt;li&gt;Centralized DB access layer&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 Introducing &lt;code&gt;DatabaseManager&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A brand-new module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;core/database_manager.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Design Idea
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Each thread gets its own SQLite connection&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connections are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created &lt;strong&gt;on demand&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Stored &lt;strong&gt;per thread&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Automatically reused inside that thread&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔐 Enforced Safety
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;check_same_thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ SAFE
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a thread tries to use another thread’s connection → &lt;strong&gt;SQLite blocks it immediately&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s what we want.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Features of &lt;code&gt;DatabaseManager&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✔ Thread-Local Connection Pooling
&lt;/h3&gt;

&lt;p&gt;Each thread has &lt;strong&gt;its own isolated connection&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Automatic Retry on Locks
&lt;/h3&gt;

&lt;p&gt;Handles SQLite’s infamous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OperationalError: database is locked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with retry + backoff logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Transaction Support
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;execute_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensures &lt;strong&gt;atomic writes&lt;/strong&gt; even under load.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Clean Shutdown
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;close_all_connections&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No leaked file handles. No corrupted DBs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 Fixing Existing Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Before: Shared Connection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_same_thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ After: Thread-Safe Manager
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.database_manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DatabaseManager&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DatabaseManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every database call now goes through &lt;strong&gt;one safe gateway&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧼 Removing Direct Cursor Access
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ UI Code Touching DB Directly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cursor&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE FROM history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Proper Encapsulation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_operations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_history&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more hidden race conditions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Stress Testing the Fix
&lt;/h2&gt;

&lt;p&gt;I didn’t trust this blindly — I &lt;strong&gt;stress tested it hard&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;5 threads&lt;/li&gt;
&lt;li&gt;50 DB operations each&lt;/li&gt;
&lt;li&gt;250 total concurrent writes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total operations: 250
Successful: 250
Failed: 0
Database records: 250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 &lt;strong&gt;Zero failures. Zero locks. Zero corruption.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Thread-Local Connections Verified
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Number of unique connections: 3
✓ Each thread has its own connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly as designed.&lt;/p&gt;




&lt;h2&gt;
  
  
  📈 Impact
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before ❌
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Random crashes&lt;/li&gt;
&lt;li&gt;Locked database errors&lt;/li&gt;
&lt;li&gt;Unsafe concurrent writes&lt;/li&gt;
&lt;li&gt;App unstable under load&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After ✅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fully thread-safe database access&lt;/li&gt;
&lt;li&gt;Stable concurrent operations&lt;/li&gt;
&lt;li&gt;No corruption risk&lt;/li&gt;
&lt;li&gt;Production-ready SQLite usage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🗂️ Files Changed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/database_manager.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New thread-safe DB layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/history.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Migrated all queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ui/main_window.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removed direct DB access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tests/test_database_threading.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stress test suite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SQLite is thread-friendly, not thread-safe&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_same_thread=False&lt;/code&gt; is a trap&lt;/li&gt;
&lt;li&gt;One connection per thread is the correct model&lt;/li&gt;
&lt;li&gt;Centralizing DB access prevents future bugs&lt;/li&gt;
&lt;li&gt;Stress tests reveal bugs unit tests won’t&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔗 Source Code
&lt;/h2&gt;

&lt;p&gt;📦 GitHub Repository:&lt;br&gt;
👉 &lt;a href="https://github.com/Mrtracker-new/Sortify" rel="noopener noreferrer"&gt;https://github.com/Mrtracker-new/Sortify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This wasn’t just a bug fix — it was a &lt;strong&gt;foundational stability upgrade&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your Python app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses SQLite&lt;/li&gt;
&lt;li&gt;Has background threads&lt;/li&gt;
&lt;li&gt;Randomly crashes under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;This pattern will save you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>python</category>
      <category>sql</category>
      <category>programming</category>
      <category>database</category>
    </item>
    <item>
      <title>Introducing QR Code Steganography: Because Normal QR Codes Are Too Mainstream</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Sun, 25 Jan 2026 14:28:27 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/introducing-qr-code-steganography-because-normal-qr-codes-are-too-mainstream-1ipj</link>
      <guid>https://forem.com/rolan_r_n_r/introducing-qr-code-steganography-because-normal-qr-codes-are-too-mainstream-1ipj</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Knew They Had
&lt;/h2&gt;

&lt;p&gt;You know what's boring? Regular QR codes. You scan them, they take you to a website. Yawn. 😴&lt;/p&gt;

&lt;p&gt;You know what's &lt;em&gt;exciting&lt;/em&gt;? QR codes that look totally normal to everyone else, but secretly contain hidden messages that only &lt;strong&gt;you&lt;/strong&gt; can read. Spy level: 100. 🕵️‍♂️&lt;/p&gt;

&lt;p&gt;So I built exactly that into InvisioVault. Because why should images have all the steganography fun?&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes This Different?
&lt;/h2&gt;

&lt;p&gt;Most QR code generators can add text. Cool. But that text is in the QR data itself - anyone with a scanner can see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InvisioVault's QR codes are double agents.&lt;/strong&gt; They have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📱 &lt;strong&gt;Public data&lt;/strong&gt; - What normal scanners see (your URL, contact info, whatever)&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Hidden secret&lt;/strong&gt; - Only visible when scanned with InvisioVault&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scan it with your phone? Goes to your website.&lt;br&gt;&lt;br&gt;
Scan it with InvisioVault? Reveals your secret message.&lt;/p&gt;

&lt;p&gt;Same QR code. Two completely different experiences. Magic? No. URL fragments? &lt;em&gt;Yes.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  How It Works (The Nerdy Bits)
&lt;/h2&gt;

&lt;p&gt;When you generate a QR code with InvisioVault, we encode it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://yourwebsite.com/#IVDATA:encrypted_secret_goes_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Here's the clever part:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normal QR scanners&lt;/strong&gt; read the URL and open your browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browsers ignore everything after the &lt;code&gt;#&lt;/code&gt;&lt;/strong&gt; (it's a URL fragment)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your website loads perfectly&lt;/strong&gt; - no weird data, no errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;InvisioVault&lt;/strong&gt; reads the &lt;em&gt;full&lt;/em&gt; QR data including the fragment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We decrypt and display your hidden message&lt;/strong&gt; 🎉&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's like hiding a secret note inside a birthday card, except the birthday card is a QR code and the note is encrypted with AES-256. As one does.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Journey: A Tale of Trial and Error
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1: Null Byte Separation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;"Let's use &lt;code&gt;\x00&lt;/code&gt; to separate public and private data!"&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Result: Some scanners URL-encoded it. URLs looked like &lt;code&gt;website.com%00garbage&lt;/code&gt;. Fail. ❌&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 2: LSB Image Steganography&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;"Hide the secret in the QR code pixels!"&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Result: Camera captures recompress images. Pixel data destroyed. Hidden message? Gone. Double fail. ❌❌&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 3: URL Fragments&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;"What if we just... use URL fragments?"&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Result: &lt;strong&gt;IT ACTUALLY WORKS.&lt;/strong&gt; ✅&lt;/p&gt;

&lt;p&gt;Sometimes the simple solution is the right solution. Sometimes you just need to fail twice first.&lt;/p&gt;


&lt;h2&gt;
  
  
  Live Camera Scanning 📷
&lt;/h2&gt;

&lt;p&gt;But wait, there's more! You can now scan QR codes &lt;strong&gt;directly with your webcam&lt;/strong&gt;. No screenshots, no uploads, just point and scan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The technical magic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5-tier progressive camera fallback (works on 95%+ devices)&lt;/li&gt;
&lt;li&gt;Dual-canvas processing:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Original canvas&lt;/strong&gt;: Preserves color for extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced canvas&lt;/strong&gt;: 2x upscaling + grayscale + 50% contrast boost&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Adaptive scan intervals (500ms → 2000ms with exponential backoff)&lt;/li&gt;
&lt;li&gt;MD5-based request caching (60-80% cache hit rate)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Translation: It works fast, on almost any device, and doesn't murder your server. Success! 🎊&lt;/p&gt;


&lt;h2&gt;
  
  
  Show Me The Goods! 📸
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Generating a QR Code
&lt;/h3&gt;

&lt;p&gt;Here's what it looks like when you create a secret QR code:&lt;/p&gt;





&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1qw3nzy1nyexp2jnlre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1qw3nzy1nyexp2jnlre.png" alt="QR generation page" width="800" height="826"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Just a normal-looking QR code generator... or is it?&lt;/em&gt; 😏&lt;/p&gt;
&lt;h3&gt;
  
  
  Scanning &amp;amp; Extracting the Secret
&lt;/h3&gt;

&lt;p&gt;And here's what happens when you scan it with InvisioVault:&lt;/p&gt;





&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dq3z8k2vgor4ag07whp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dq3z8k2vgor4ag07whp.png" alt="QR code scan result" width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Public data? Check. Hidden secret message? Double check. Mission accomplished!&lt;/em&gt; ✨&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Is Actually Useful
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Okay cool, but when would I actually use this?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Glad you asked! Here are some totally-not-made-up scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Business Cards&lt;/strong&gt; 📇&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Your LinkedIn profile&lt;/li&gt;
&lt;li&gt;Secret: Your actual phone number (for select people who scan it right)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Event Tickets&lt;/strong&gt; 🎫&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Event website&lt;/li&gt;
&lt;li&gt;Secret: VIP access code or exclusive content link&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Product Packaging&lt;/strong&gt; 📦&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Product information&lt;/li&gt;
&lt;li&gt;Secret: Warranty details or customer support portal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Marketing Materials&lt;/strong&gt; 📰&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Standard landing page&lt;/li&gt;
&lt;li&gt;Secret: Special discount code for InvisioVault users&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Just Because You Can&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public: Rick Roll link&lt;/li&gt;
&lt;li&gt;Secret: Actual content you wanted to share&lt;/li&gt;
&lt;li&gt;(Okay this one might be real)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  Performance Stats 📊
&lt;/h2&gt;

&lt;p&gt;Because numbers make everything more credible:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;QR Detection Rate&lt;/td&gt;
&lt;td&gt;80%+ in good lighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device Compatibility&lt;/td&gt;
&lt;td&gt;95%+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend Load Reduction&lt;/td&gt;
&lt;td&gt;60-80% (thanks to caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal Scanner Compatibility&lt;/td&gt;
&lt;td&gt;100% (they just ignore the secret)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times I Thought This Wouldn't Work&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times I Was Wrong&lt;/td&gt;
&lt;td&gt;1 (this time it worked!)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  The Code Journey
&lt;/h2&gt;

&lt;p&gt;This feature went through more iterations than I'd like to admit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- Attempt 1: Data encoding with null bytes
&lt;/span&gt;&lt;span class="gi"&gt;+ Attempt 2: LSB pixel steganography
+ Attempt 3: URL fragment encoding ✓
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shoutout to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Segno&lt;/strong&gt; for QR generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pyzbar&lt;/strong&gt; for QR scanning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL fragments&lt;/strong&gt; for existing and being perfect for this&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coffee&lt;/strong&gt; for existing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Yourself!
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://github.com/Mrtracker-new/InvisioVault" rel="noopener noreferrer"&gt;InvisioVault&lt;/a&gt; and give it a spin!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a QR code with a hidden message&lt;/li&gt;
&lt;li&gt;Scan it with your phone - see the public data&lt;/li&gt;
&lt;li&gt;Scan it with InvisioVault - see the secret&lt;/li&gt;
&lt;li&gt;Feel like a spy&lt;/li&gt;
&lt;li&gt;Repeat until you've hidden secrets everywhere&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Some ideas I'm considering (read: may or may not implement):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎨 More QR customization options (logos, colors, patterns)&lt;/li&gt;
&lt;li&gt;📊 Analytics to see who scanned your QR codes&lt;/li&gt;
&lt;li&gt;🔗 QR code chaining (scan one, get led to another, treasure hunt style!)&lt;/li&gt;
&lt;li&gt;🎭 Multiple hidden messages in one QR (because why not?)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this feature taught me several things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The simple solution is often hidden behind two complicated ones&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL fragments are more useful than you think&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Camera APIs are surprisingly well-supported now&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSB steganography is fragile and I should stop trying to make it work for everything&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you made it this far, congrats! You're either really interested in steganography, procrastinating on actual work, or both. Either way, thanks for reading! 🎉&lt;/p&gt;

&lt;p&gt;Now go forth and hide secrets in QR codes. The world is your oyster. Or QR code. Same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Details (For the Curious)
&lt;/h2&gt;

&lt;p&gt;If you want to dive deep into how this works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES-256-CBC with PBKDF2 key derivation&lt;/li&gt;
&lt;li&gt;100,000 iterations (industry standard)&lt;/li&gt;
&lt;li&gt;Random 16-byte salt and IV for each generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;QR Format:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{public_data}#IVDATA:{base64_encrypted_secret}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Camera Processing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MediaStream API for webcam access&lt;/li&gt;
&lt;li&gt;Canvas API for frame capture and enhancement&lt;/li&gt;
&lt;li&gt;Dual-canvas approach for quality preservation&lt;/li&gt;
&lt;li&gt;Real-time QR detection with adaptive intervals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance Optimizations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MD5 hashing for request deduplication&lt;/li&gt;
&lt;li&gt;In-memory caching with 1-second TTL&lt;/li&gt;
&lt;li&gt;Exponential backoff to reduce unnecessary processing&lt;/li&gt;
&lt;li&gt;Progressive camera configuration fallback&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Made with 💜 by Rolan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - If you find any bugs, they're not bugs, they're undocumented features. But please tell me anyway.&lt;/em&gt; 😅&lt;/p&gt;




&lt;h2&gt;
  
  
  Share Your Creations!
&lt;/h2&gt;

&lt;p&gt;If you create something cool with this feature, tag me! I'd love to see what creative uses people come up with for hidden QR messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Mrtracker-new" rel="noopener noreferrer"&gt;@Mrtracker-new&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Email:&lt;/strong&gt; &lt;a href="mailto:rolanlobo901@gmail.com"&gt;rolanlobo901@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy hiding! 🎭✨&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>python</category>
      <category>react</category>
    </item>
    <item>
      <title>Stop Making Your Users Play 'Will It Fit?'</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Fri, 23 Jan 2026 16:43:07 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/stop-making-your-users-play-will-it-fit-m8c</link>
      <guid>https://forem.com/rolan_r_n_r/stop-making-your-users-play-will-it-fit-m8c</guid>
      <description>&lt;h2&gt;
  
  
  Stop Making Your Users Play 'Will It Fit?'
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;My steganography app lets users hide files inside images. Cool, right? Except users kept getting this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Error: Image doesn't have enough capacity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; uploading everything. &lt;strong&gt;After&lt;/strong&gt; waiting. &lt;strong&gt;After&lt;/strong&gt; clicking the button.&lt;/p&gt;

&lt;p&gt;So they'd try a bigger image. Upload again. Click. Fail. Repeat until rage-quit. &lt;/p&gt;

&lt;p&gt;Not great for retention. 📉&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;I added a real-time capacity calculator that shows users if their file will fit &lt;strong&gt;before&lt;/strong&gt; they click anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2a4epm6s87fiu8yafph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2a4epm6s87fiu8yafph.png" alt="Capacity check" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's just a React component that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculates capacity when files are selected&lt;/li&gt;
&lt;li&gt;Shows a color-coded progress bar&lt;/li&gt;
&lt;li&gt;Tells users upfront: "✅ File will fit comfortably" or "❌ Too big buddy"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Code (The Interesting Bits)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt; - Calculate how much fits in an image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@api.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/calculate-capacity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_capacity&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_pixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getdata&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

    &lt;span class="c1"&gt;# LSB stego: 3 bits per pixel / 8 = bytes
&lt;/span&gt;    &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_pixels&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;totalCapacityBytes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt; - Debounce so we don't spam the API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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="c1"&gt;// Wait 500ms after user stops typing&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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="nf"&gt;calculateCapacity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Pretty Part&lt;/strong&gt; - Color-coded status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;percentage&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&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="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too large!&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;90&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="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚠️&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Barely fits&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;70&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="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚡&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;High capacity&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fits comfortably&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;h2&gt;
  
  
  The Plot Twist
&lt;/h2&gt;

&lt;p&gt;I deployed it. Users loved it. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Failed to calculate capacity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out my 50 req/hour rate limit couldn't handle users actually &lt;em&gt;using&lt;/em&gt; the feature. Whoops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Bumped to 100 req/hour + added debouncing = happy users.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero&lt;/strong&gt; "file too large" complaints (down from many)&lt;/li&gt;
&lt;li&gt;Users understand capacity limits without reading docs&lt;/li&gt;
&lt;li&gt;App feels way more professional&lt;/li&gt;
&lt;li&gt;I learned about debouncing the hard way&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Show, don't tell&lt;/strong&gt; - Real-time feedback &amp;gt; documentation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Debounce everything&lt;/strong&gt; - Users type fast. Your API can't keep up. Plan accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Polish matters&lt;/strong&gt; - That shine animation on the progress bar? Totally unnecessary. Users love it anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Watch your rate limits&lt;/strong&gt; - Good features get used. A lot. Be ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full code is on &lt;a href="https://github.com/Mrtracker-new/InvisioVault" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Feel free to steal, improve, or roast my implementation. &lt;/p&gt;

&lt;p&gt;Sometimes the best features aren't the flashy new capabilities - they're the tiny UX tweaks that make users go "oh thank god, finally."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What small feature transformed your app's UX?&lt;/strong&gt; Drop it in the comments! 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building steganography tools at 3 AM with too much coffee ☕&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Follow: &lt;a href="https://github.com/Mrtracker-new" rel="noopener noreferrer"&gt;@Mrtracker-new&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ux</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Lying to Your Users With Spinning Circles</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Thu, 22 Jan 2026 16:28:32 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/stop-lying-to-your-users-with-spinning-circles-16lb</link>
      <guid>https://forem.com/rolan_r_n_r/stop-lying-to-your-users-with-spinning-circles-16lb</guid>
      <description>&lt;h2&gt;
  
  
  I Gave My Loading Screens a Glow-Up (And You Can Too!) 💅
&lt;/h2&gt;

&lt;p&gt;You know that awkward moment when your app is loading and all your users see is a lonely spinning circle? Yeah, me too. So I decided to do something about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Spinners Are Liars 🤥
&lt;/h2&gt;

&lt;p&gt;Picture this: You're building a file encryption app (yes, that's what I do for fun), and your backend is sleeping on Render's free tier. When someone tries to access a file, they click the button and... nothing. Just a spinner. For 15 seconds.&lt;/p&gt;

&lt;p&gt;No feedback. No progress. Just vibes. ✨&lt;/p&gt;

&lt;p&gt;Users are left wondering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it working?&lt;/li&gt;
&lt;li&gt;Did I break it?&lt;/li&gt;
&lt;li&gt;Should I refresh?&lt;/li&gt;
&lt;li&gt;Is it time to panic yet?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spoiler: They always panic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Multi-Stage Loading Indicators 🎯
&lt;/h2&gt;

&lt;p&gt;Instead of a basic spinner that screams "I'm working, trust me bro," I built a loading component that actually &lt;strong&gt;communicates&lt;/strong&gt; with users. Here's what it does:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔵 Stage 1: Connecting
&lt;/h3&gt;

&lt;p&gt;"Waking up server..." (because yes, it's literally waking up from a nap)&lt;/p&gt;

&lt;h3&gt;
  
  
  🟣 Stage 2: Authenticating
&lt;/h3&gt;

&lt;p&gt;"Authenticating request..." (fancy words for checking if you're allowed in)&lt;/p&gt;

&lt;h3&gt;
  
  
  🟡 Stage 3: Decrypting
&lt;/h3&gt;

&lt;p&gt;"Decrypting file..." (the actual magic happens here)&lt;/p&gt;

&lt;h3&gt;
  
  
  🟢 Stage 4: Rendering
&lt;/h3&gt;

&lt;p&gt;"Rendering preview..." (almost there!)&lt;/p&gt;

&lt;p&gt;Each stage gets its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Icon&lt;/strong&gt; (Server → Shield → Lock → Eye)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color&lt;/strong&gt; (Blue → Purple → Amber → Green)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress percentage&lt;/strong&gt; (0% → 25% → 50% → 75% → 100%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth animations&lt;/strong&gt; (because we're fancy like that)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Code: Let's Get Spicy 🌶️
&lt;/h2&gt;

&lt;p&gt;I created a reusable &lt;code&gt;LoadingStages&lt;/code&gt; component that takes the current stage and progress as props:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoadingStages&lt;/span&gt;
  &lt;span class="na"&gt;currentStage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"connecting"&lt;/span&gt;
  &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;estimatedTime&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;15&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;The component cycles through stages automatically based on your app's actual loading process. No fake progress bars here! (We're honest people.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Cold Start Detection 🥶
&lt;/h3&gt;

&lt;p&gt;The best part? It detects when the server is cold-starting and shows an estimated time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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="nx"&gt;responseTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setEstimatedTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "~15s" appears on screen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, users know they're in for a wait and can grab their coffee ☕&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: Happy Users 😊
&lt;/h2&gt;

&lt;p&gt;Instead of anxious button-mashing, users now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See exactly what's happening&lt;/strong&gt; at each stage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know how long it'll take&lt;/strong&gt; during cold starts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feel confident&lt;/strong&gt; the app is working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't spam refresh&lt;/strong&gt; (my server thanks them)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus, it looks &lt;em&gt;gorgeous&lt;/em&gt;. The glassmorphic design with color transitions makes loading actually pleasant to watch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Try It? 🚀
&lt;/h2&gt;

&lt;p&gt;Check out the full implementation in my repo:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/Mrtracker-new/BAR_RYY" rel="noopener noreferrer"&gt;BAR_RYY&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The magic lives in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;frontend/src/components/LoadingStages.jsx&lt;/code&gt; (the star of the show)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frontend/src/components/SharePage.jsx&lt;/code&gt; (where it's integrated)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways 💡
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loading screens are prime real estate&lt;/strong&gt; - Use them to communicate!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Users tolerate waits better&lt;/strong&gt; when they know what's happening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start detection&lt;/strong&gt; turns frustration into understanding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pretty animations&lt;/strong&gt; make everything better (scientific fact)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts 🎨
&lt;/h2&gt;

&lt;p&gt;We spend so much time optimizing for speed, but sometimes the wait is unavoidable (looking at me, free tier cold starts). When you can't eliminate the wait, make it &lt;strong&gt;informative&lt;/strong&gt; and &lt;strong&gt;beautiful&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your users will appreciate knowing that yes, the app is working, and no, they don't need to panic.&lt;/p&gt;

&lt;p&gt;Now if you'll excuse me, I'm off to give all my loading screens glow-ups. This is addicting. 💅✨&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's the most creative loading indicator you've seen?&lt;/strong&gt; Drop it in the comments! 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - If you're building anything with file encryption, 2FA, or self-destructing links, definitely check out the repo. It's got some cool security features too!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>react</category>
      <category>ux</category>
    </item>
    <item>
      <title>How I Stopped Worrying and Learned to Love Organization</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Wed, 21 Jan 2026 15:57:03 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/how-i-stopped-worrying-and-learned-to-love-organization-18el</link>
      <guid>https://forem.com/rolan_r_n_r/how-i-stopped-worrying-and-learned-to-love-organization-18el</guid>
      <description>&lt;h2&gt;
  
  
  The Before Times (aka The Dark Ages)
&lt;/h2&gt;

&lt;p&gt;Picture this: You open your backend folder. It's like walking into a teenager's room. Files everywhere. No rhyme. No reason. Just pure, unadulterated chaos.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;database.py&lt;/code&gt; living next to &lt;code&gt;upload.py&lt;/code&gt;, hanging out with &lt;code&gt;security.py&lt;/code&gt;, while &lt;code&gt;cleanup.py&lt;/code&gt; just vibes in the corner. Everyone's at the same level, nobody has their own space, and finding anything requires either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A prayer 🙏&lt;/li&gt;
&lt;li&gt;Ctrl+P like your life depends on it&lt;/li&gt;
&lt;li&gt;Or acceptance that you'll spend 10 minutes scrolling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar? Yeah, I thought so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Intervention
&lt;/h2&gt;

&lt;p&gt;One day, I looked at my backend folder and had an epiphany. Or maybe it was just caffeine-induced clarity. Either way, I realized something profound:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My files were adults. They deserved their own rooms.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I did what any responsible developer would do: I created folders. ACTUAL. FOLDERS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
├── core/           # The VIPs
├── services/       # The workers
├── storage/        # The file hoarders
└── utils/          # The helpful folks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revolutionary? No. Overdue? Absolutely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great Migration (aka Moving Day)
&lt;/h2&gt;

&lt;p&gt;Moving files is like playing Tetris, but with imports. You move one file and suddenly 47 things break. It's beautiful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Create folders&lt;br&gt;&lt;br&gt;
✅ Easy mode&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Move files into folders&lt;br&gt;&lt;br&gt;
✅ Still easy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Update all the imports&lt;br&gt;&lt;br&gt;
⚠️ &lt;strong&gt;BOSS BATTLE INITIATED&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;core.database&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;

&lt;span class="c1"&gt;# My brain
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;please.work.i.beg.you&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sanity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I spent quality time with find-and-replace. We bonded. We grew together. I may have shed a tear when all the imports finally worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Changed
&lt;/h2&gt;

&lt;p&gt;Here's what I inflicted upon my codebase:&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 &lt;strong&gt;core/&lt;/strong&gt; - The Foundation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;database.py&lt;/code&gt; - Because databases are core, obviously&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.py&lt;/code&gt; - All the environment variables in one place&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;auth.py&lt;/code&gt; - Security matters, folks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ⚙️ &lt;strong&gt;services/&lt;/strong&gt; - The Doers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;upload_service.py&lt;/code&gt; - Handles uploads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;share_service.py&lt;/code&gt; - Handles sharing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cleanup_service.py&lt;/code&gt; - Cleans up after everyone else (the real MVP)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💾 &lt;strong&gt;storage/&lt;/strong&gt; - The Packrats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file_manager.py&lt;/code&gt; - Manages files (shocking, I know)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;storage_handler.py&lt;/code&gt; - Stores things&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛠️ &lt;strong&gt;utils/&lt;/strong&gt; - The Helpers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;security.py&lt;/code&gt; - Security utilities&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;validators.py&lt;/code&gt; - Validates stuff&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;helpers.py&lt;/code&gt; - Helps with... stuff&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Fallout
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Good:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding files is now actually possible&lt;/li&gt;
&lt;li&gt;New team members don't look at me with fear and confusion&lt;/li&gt;
&lt;li&gt;I can pretend I know what I'm doing&lt;/li&gt;
&lt;li&gt;Separation of concerns is no longer just a buzzword I throw around&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Bad:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My muscle memory is completely wrecked&lt;/li&gt;
&lt;li&gt;I still type old import paths&lt;/li&gt;
&lt;li&gt;Had to update the mental map in my brain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Ugly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That one time I forgot to update an import and the server crashed in production&lt;/li&gt;
&lt;li&gt;Just kidding! (Or am I? 😅)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with folders.&lt;/strong&gt; Don't be like me. Don't wait until you have 30 files in the root directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your IDE's refactoring tools are your friends.&lt;/strong&gt; Use them. Love them. They'll save you from import hell.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test after moving EVERYTHING.&lt;/strong&gt; I mean it. Test it all. Then test it again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's never too late to organize.&lt;/strong&gt; Even if your codebase is a disaster, you can fix it. I believe in you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document the structure.&lt;/strong&gt; Future you will thank present you. Past you is already disappointed, but that's okay.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Commit Message That Started It All
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refactor: finally convinced my files to use folders like adults

- Created proper folder structure (core, services, storage, utils)
- Moved files from root to appropriate directories
- Updated all import statements
- Celebrated with coffee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Is my backend perfect now? No.&lt;br&gt;&lt;br&gt;
Is it better? Absolutely.&lt;br&gt;&lt;br&gt;
Did I learn something? Maybe.&lt;br&gt;&lt;br&gt;
Will I do this again? Already planning the frontend refactor.&lt;/p&gt;

&lt;p&gt;Remember folks: &lt;strong&gt;Organization is not about being perfect. It's about being less chaotic than you were yesterday.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you're sitting there with a messy backend folder, take this as your sign. Your files deserve their own rooms too.&lt;/p&gt;

&lt;p&gt;Now go forth and mkdir! 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you ever done a massive refactor? Share your horror stories in the comments! I promise mine was worse. Probably.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>coding</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why Let Users Choose Between Being Nice and Being Paranoid? 🔄</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Tue, 20 Jan 2026 18:17:41 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/why-let-users-choose-between-being-nice-and-being-paranoid-5c8n</link>
      <guid>https://forem.com/rolan_r_n_r/why-let-users-choose-between-being-nice-and-being-paranoid-5c8n</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: My App Had Commitment Issues 💔
&lt;/h2&gt;

&lt;p&gt;So I built this file-sharing web-app called &lt;a href="https://bar-rnr.vercel.app/" rel="noopener noreferrer"&gt;BAR Web&lt;/a&gt; that lets you send files that self-destruct after being viewed (think Mission Impossible but for PDFs). Cool, right?&lt;/p&gt;

&lt;p&gt;But here's where things got weird: I added &lt;strong&gt;two&lt;/strong&gt; features to control how page refreshes work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;View Refresh Threshold&lt;/strong&gt; - "Hey, if the same person refreshes within 5 minutes, don't count it as a new view"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Refresh Interval&lt;/strong&gt; - "Force the page to reload every 30 seconds so the file disappears"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And like a fool, I let users enable &lt;strong&gt;both at the same time&lt;/strong&gt;. 🤦‍♂️&lt;/p&gt;

&lt;h2&gt;
  
  
  What Could Go Wrong?
&lt;/h2&gt;

&lt;p&gt;Imagine this conversation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "I want to be nice! Let people refresh without wasting views!"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Also User:&lt;/strong&gt; "But also force them to reload every 30 seconds!"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Me:&lt;/strong&gt; "...that's like wearing a belt AND suspenders, my friend."&lt;/p&gt;

&lt;p&gt;It made zero sense. If you're auto-reloading the page, why care about refresh thresholds? If you're being forgiving with refreshes, why force reloads?&lt;/p&gt;

&lt;p&gt;It was like trying to be both chill and paranoid at the same time. Pick a lane, buddy!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Couples Therapy for UI Elements 💑
&lt;/h2&gt;

&lt;p&gt;I made them &lt;strong&gt;mutually exclusive&lt;/strong&gt; using radio buttons. Now users have to choose ONE:&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1: View Refresh Threshold (The Nice One)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I trust you not to spam refresh. Take your time!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recipients who might have slow internet 📶&lt;/li&gt;
&lt;li&gt;People who need to scroll through long documents&lt;/li&gt;
&lt;li&gt;When you're feeling generous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 minutes (every refresh counts - default)&lt;/li&gt;
&lt;li&gt;5 minutes (recommended)&lt;/li&gt;
&lt;li&gt;Up to 1 hour (very forgiving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
User refreshes 3 times in 4 minutes → Still counts as 1 view 🎯&lt;/p&gt;


&lt;h3&gt;
  
  
  Option 2: Auto-Refresh Interval (The Paranoid One)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"You have 30 seconds. Make them count. ⏱️"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Super sensitive stuff&lt;/li&gt;
&lt;li&gt;Time-limited access codes&lt;/li&gt;
&lt;li&gt;Maximum security theater&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 seconds (ruthless)&lt;/li&gt;
&lt;li&gt;30 seconds (recommended)&lt;/li&gt;
&lt;li&gt;Up to 5 minutes (generous for paranoia mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
User opens file → Has 30 seconds to read → Page reloads → File might be gone 💨&lt;/p&gt;
&lt;h2&gt;
  
  
  The Implementation (For the Nerds) 🤓
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Frontend Magic
&lt;/h3&gt;

&lt;p&gt;I used radio buttons styled like cards (same pattern as my storage mode selector):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When "View Refresh Threshold" is selected&lt;/span&gt;
&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onRulesChange&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;viewRefreshMinutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Enable this one&lt;/span&gt;
  &lt;span class="na"&gt;autoRefreshSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;          &lt;span class="c1"&gt;// Disable the other&lt;/span&gt;
&lt;span class="p"&gt;})}&lt;/span&gt;

&lt;span class="c1"&gt;// When "Auto-Refresh Interval" is selected  &lt;/span&gt;
&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onRulesChange&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;viewRefreshMinutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Disable this one&lt;/span&gt;
  &lt;span class="na"&gt;autoRefreshSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;         &lt;span class="c1"&gt;// Enable the other&lt;/span&gt;
&lt;span class="p"&gt;})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I show the dropdown settings &lt;strong&gt;only&lt;/strong&gt; for the selected option. One choice, one settings panel. Clean and simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Wisdom
&lt;/h3&gt;

&lt;p&gt;The backend was already handling both features separately. I just had to make sure only one value is non-zero at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In the database
&lt;/span&gt;&lt;span class="n"&gt;view_refresh_minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;auto_refresh_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# Only one should be &amp;gt; 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fingerprinting logic checks if enough time has passed, and the auto-refresh header tells the browser when to reload. Easy peasy.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't give users contradictory choices&lt;/strong&gt; - It's our job to prevent foot-shooting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI should reflect reality&lt;/strong&gt; - If two options fight each other, make them exclusive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good UX is about constraints&lt;/strong&gt; - Sometimes the best feature is the one you don't include&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radio buttons &amp;gt; Checkboxes&lt;/strong&gt; for mutually exclusive stuff (duh)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Result 🎉
&lt;/h2&gt;

&lt;p&gt;Now my app has a clear UI that says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Pick your personality: Nice or Paranoid. You can't be both, Karen."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Users love it. No more confusion. No more contradictory settings. Just clean, simple choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself!
&lt;/h2&gt;

&lt;p&gt;The feature is live at &lt;a href="https://bar-rnr.vercel.app/" rel="noopener noreferrer"&gt;bar-rnr.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you want to see the code or contribute:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/Mrtracker-new/BAR_RYY" rel="noopener noreferrer"&gt;GitHub: BAR_RYY&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fun fact:&lt;/strong&gt; I committed this in 4 separate commits with increasingly silly commit messages. Because if you're not having fun while coding, what's the point? 😄&lt;/p&gt;

&lt;p&gt;The last commit was literally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Docs got the memo! 📝 README and FEATURES.md now explain the new Smart Refresh Control with examples, emojis, and a healthy reminder that you can't enable both options because that'd be like wearing a belt AND suspenders 👖"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have you ever built conflicting features into your app? Share your "oops" moments in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>ux</category>
    </item>
    <item>
      <title>How I Accidentally Became a Performance Guru</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Sun, 18 Jan 2026 15:51:58 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/how-i-accidentally-became-a-performance-guru-3lla</link>
      <guid>https://forem.com/rolan_r_n_r/how-i-accidentally-became-a-performance-guru-3lla</guid>
      <description>&lt;h2&gt;
  
  
  The Classic Beginner Move: Doing Everything Backwards
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you're building your portfolio as a beginner and you suddenly think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“THIS is going to be legendary.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yeah. That was me.&lt;/p&gt;

&lt;p&gt;I planned everything with &lt;em&gt;extreme seriousness&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Cool animations
&lt;/li&gt;
&lt;li&gt;✅ Smooth transitions
&lt;/li&gt;
&lt;li&gt;✅ Project showcase
&lt;/li&gt;
&lt;li&gt;✅ Fancy tech stack badges
&lt;/li&gt;
&lt;li&gt;✅ Blog section
&lt;/li&gt;
&lt;li&gt;❌ Resume… eventually?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the thing about being a beginner:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You overthink the simple stuff&lt;br&gt;&lt;br&gt;
and underthink the important stuff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spent &lt;strong&gt;days&lt;/strong&gt; perfecting my hero section animation.&lt;/p&gt;

&lt;p&gt;But adding my actual resume to my portfolio?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Eh… I’ll do it later.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So naturally, I added my resume &lt;strong&gt;dead last&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You know —&lt;br&gt;&lt;br&gt;
&lt;strong&gt;the ONE thing employers might actually want to see&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the time I got to it, I had already:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;implemented &lt;strong&gt;three color themes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;refactored my components &lt;strong&gt;twice&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;memorized half of the React docs&lt;/li&gt;
&lt;li&gt;emotionally bonded with my CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Classic beginner behavior. 🤦‍♂️&lt;/p&gt;


&lt;h2&gt;
  
  
  The React-PDF Rabbit Hole 🕳️🐇
&lt;/h2&gt;

&lt;p&gt;When I &lt;em&gt;finally&lt;/em&gt; decided to add my resume, I told myself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’m a &lt;strong&gt;developer&lt;/strong&gt; now.&lt;br&gt;&lt;br&gt;
I must do this the &lt;strong&gt;developer way&lt;/strong&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I did what any self-respecting beginner does:&lt;/p&gt;

&lt;p&gt;🔍 Googled: &lt;em&gt;“How to add PDF to React website”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And there it was.&lt;/p&gt;

&lt;p&gt;✨ &lt;strong&gt;react-pdf&lt;/strong&gt; ✨&lt;/p&gt;

&lt;p&gt;A whole library just for PDFs?&lt;/p&gt;

&lt;p&gt;Perfect.&lt;br&gt;&lt;br&gt;
Libraries = Professional.&lt;br&gt;&lt;br&gt;
More code = Smarter developer.&lt;br&gt;&lt;br&gt;
Right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Look at me using a library 😎&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Rolan_Resume.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Document&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I felt unstoppable.&lt;/p&gt;

&lt;p&gt;I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed dependencies&lt;/li&gt;
&lt;li&gt;Configured webpack&lt;/li&gt;
&lt;li&gt;read &lt;strong&gt;15 Stack Overflow answers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Fought canvas rendering errors&lt;/li&gt;
&lt;li&gt;Questioned my life choices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was &lt;strong&gt;REAL development&lt;/strong&gt;, right?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wake-Up Call 😱
&lt;/h2&gt;

&lt;p&gt;Then I ran &lt;strong&gt;Lighthouse&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance: 67&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💀💀💀&lt;/p&gt;

&lt;p&gt;My beautifully crafted portfolio — the one I spent weeks polishing — was being absolutely &lt;strong&gt;murdered&lt;/strong&gt; by a PDF renderer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile users were probably aging in real time&lt;/li&gt;
&lt;li&gt;Resume loading slower than my motivation on Mondays&lt;/li&gt;
&lt;li&gt;And for WHAT?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wasn’t interactive&lt;/li&gt;
&lt;li&gt;didn’t zoom properly&lt;/li&gt;
&lt;li&gt;took forever to load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had officially used a &lt;strong&gt;sledgehammer to hang a photo frame&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The “Duh” Moment: WebP to the Rescue 🧠✨
&lt;/h2&gt;

&lt;p&gt;One day, while optimizing my project images (you know… the things I optimized &lt;em&gt;before&lt;/em&gt; my resume 🙃), I converted everything to &lt;strong&gt;WebP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;File sizes dropped like my hopes during a failed &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And suddenly…&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;It hit me.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Wait…&lt;br&gt;
Why am I rendering a PDF…&lt;br&gt;
when I could just show an image?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤯🤯🤯&lt;/p&gt;

&lt;p&gt;Groundbreaking thinking, I know.&lt;br&gt;
Someone call the Nobel committee.&lt;/p&gt;

&lt;p&gt;So I did the most &lt;em&gt;un-developer&lt;/em&gt; thing possible:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opened my resume PDF&lt;/li&gt;
&lt;li&gt;Exported it as a high-quality image&lt;/li&gt;
&lt;li&gt;Converted it to &lt;strong&gt;WebP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Deleted &lt;code&gt;react-pdf&lt;/code&gt; like it owed me money&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Replaced all of this complexity with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/resume-preview.webp"&lt;/span&gt; 
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Resume Preview"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;No library.&lt;br&gt;
No config.&lt;br&gt;
No pain.&lt;br&gt;
Just ✨ a native &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag no libraries involved ✨.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results Were… Embarrassingly Good 😐
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before (react-pdf)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 Bundle size: &lt;strong&gt;+500KB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⏱️ Load time (3G): &lt;strong&gt;3.2s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🎨 Lighthouse score: &lt;strong&gt;67&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;😤 Stress level: &lt;strong&gt;High&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After (WebP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 File size: &lt;strong&gt;~45KB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⏱️ Load time (3G): &lt;strong&gt;0.3s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🎨 Lighthouse score: &lt;strong&gt;95&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;😎 Stress level: &lt;strong&gt;Chill&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I accidentally made my site &lt;strong&gt;10x faster&lt;/strong&gt; by doing the &lt;strong&gt;simplest thing possible&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Taught Me (Besides Humility)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Not Everything Needs a Library
&lt;/h3&gt;

&lt;p&gt;Just because a library exists doesn’t mean you need it.&lt;/p&gt;

&lt;p&gt;Sometimes the browser already does the job perfectly fine.&lt;/p&gt;

&lt;p&gt;Using a library for this felt like buying a &lt;strong&gt;smart electric can opener&lt;/strong&gt;…&lt;br&gt;
when you have hands.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ WebP Is a Game Changer
&lt;/h3&gt;

&lt;p&gt;If you’re not using WebP yet… why? 😭&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;25–35% smaller than PNG/JPEG&lt;/li&gt;
&lt;li&gt;Better quality at lower sizes&lt;/li&gt;
&lt;li&gt;Supported by all modern browsers
&lt;em&gt;(sorry IE11, this party isn’t for you)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebP is basically a &lt;strong&gt;free performance upgrade&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3️⃣ The Beginner Advantage 🧠
&lt;/h3&gt;

&lt;p&gt;As a beginner, I wasn’t emotionally attached to “the right way”.&lt;/p&gt;

&lt;p&gt;When something didn’t work well, I didn’t defend it.&lt;/p&gt;

&lt;p&gt;I just went:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This sucks.”&lt;br&gt;
&lt;em&gt;Delete.&lt;/em&gt;&lt;br&gt;
Try something simpler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That mindset saved my performance.&lt;/p&gt;




&lt;h3&gt;
  
  
  4️⃣ Performance &amp;gt; Complexity
&lt;/h3&gt;

&lt;p&gt;No recruiter will ever say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Wow… you used react-pdf.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But they &lt;strong&gt;will notice&lt;/strong&gt; when your site loads faster than their coffee machine.&lt;/p&gt;

&lt;p&gt;Users don’t see your code.&lt;br&gt;
They &lt;strong&gt;feel&lt;/strong&gt; your performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Irony of It All 🎭
&lt;/h2&gt;

&lt;p&gt;I thought being a “real developer” meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more libraries&lt;/li&gt;
&lt;li&gt;more configs&lt;/li&gt;
&lt;li&gt;more complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the biggest improvement to my portfolio came from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Converting a file&lt;br&gt;
and using an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the &lt;strong&gt;best code&lt;/strong&gt;&lt;br&gt;
is the code you &lt;strong&gt;don’t write&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How You Can Do This Too (Dead Simple)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Export your resume as a high-quality image&lt;/li&gt;
&lt;li&gt;Convert it to &lt;strong&gt;WebP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"resume-preview.webp"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Resume"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Still offer the PDF download:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/resume.pdf"&lt;/span&gt; &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download PDF&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. 🎉&lt;/p&gt;

&lt;p&gt;You’ve now avoided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF.js headaches&lt;/li&gt;
&lt;li&gt;Webpack nightmares&lt;/li&gt;
&lt;li&gt;“canvas is not defined” errors&lt;/li&gt;
&lt;li&gt;Performance crimes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a portfolio as a beginner is basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;doing things backwards&lt;/li&gt;
&lt;li&gt;learning the hard way&lt;/li&gt;
&lt;li&gt;accidentally discovering best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I added my resume last.&lt;br&gt;
I over-engineered a simple problem.&lt;br&gt;
But I learned something valuable:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simple beats fancy.&lt;br&gt;
Fast beats clever.&lt;br&gt;
Users beat libraries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If my Lighthouse score went from &lt;strong&gt;67 → 95&lt;/strong&gt; just by using WebP…&lt;/p&gt;

&lt;p&gt;Imagine what yours could be.&lt;/p&gt;

&lt;p&gt;Now if you’ll excuse me, I need to refactor something that’s working perfectly fine —&lt;br&gt;
you know, as developers do. 😅&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;br&gt;
Tried to be fancy with &lt;code&gt;react-pdf&lt;/code&gt;, murdered performance.&lt;br&gt;
Switched to WebP image preview, became accidentally fast.&lt;br&gt;
WebP is magic. Simple is better.&lt;/p&gt;

&lt;p&gt;Got similar over-engineering stories?&lt;br&gt;
Drop them in the comments — let’s laugh at our beginner mistakes together 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. Yes, my resume is still downloadable as a PDF.&lt;br&gt;
I’m not a monster. But the preview?&lt;br&gt;
That’s pure WebP goodness.&lt;/em&gt; 😌&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>performance</category>
      <category>react</category>
    </item>
    <item>
      <title>This message will self-destruct in 5 seconds...</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Tue, 16 Dec 2025 17:44:01 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/this-message-will-self-destruct-in-5-seconds-505i</link>
      <guid>https://forem.com/rolan_r_n_r/this-message-will-self-destruct-in-5-seconds-505i</guid>
      <description>&lt;p&gt;You know that scene in Mission Impossible where Ethan Hunt gets his briefing and then—&lt;em&gt;poof&lt;/em&gt;—the tape/phone/hologram bursts into flames? &lt;/p&gt;

&lt;p&gt;Yeah, I wanted that. But for files. On the internet. Without the fire hazard. 🔥&lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;BAR-Web&lt;/strong&gt; (Burn After Reading) - a file-sharing app where your secrets actually stay secret and then disappear forever. No "oops, I can recover that from the recycle bin." No "let me just use this recovery tool." Just... &lt;strong&gt;gone&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Origin Story (aka "Why Did I Do This?")
&lt;/h2&gt;

&lt;p&gt;It started with a simple problem: I needed to share a password with someone. Email? Nah, that stays in their inbox forever. WhatsApp? Now it's on their cloud backup. Signal? Better, but still... what if they screenshot it?&lt;/p&gt;

&lt;p&gt;I wanted something that would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ Self-destruct after being read&lt;/li&gt;
&lt;li&gt;✅ Have ACTUAL security (not just vibes)&lt;/li&gt;
&lt;li&gt;✅ Give ME control over who sees what, and when&lt;/li&gt;
&lt;li&gt;✅ Make me feel like a secret agent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spoiler: No good free options existed. So I made one. Twice. (First a desktop exe, then this web version because I got addicted to the idea.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does It Actually Do?
&lt;/h2&gt;

&lt;p&gt;Think &lt;strong&gt;Snapchat for files&lt;/strong&gt;, but with &lt;strong&gt;actual teeth&lt;/strong&gt; and &lt;strong&gt;bank-level encryption&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the deal:&lt;/p&gt;

&lt;h3&gt;
  
  
  📤 Upload Anything
&lt;/h3&gt;

&lt;p&gt;PDFs, images, videos, your secret cookie recipe—up to 100MB. No judgment.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔒 Fort Knox Encryption
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AES-256&lt;/strong&gt; encryption (the same stuff governments use to protect classified docs). Your files are turned into digital gibberish that would take a supercomputer billions of years to crack. No pressure.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔑 Zero-Knowledge Security
&lt;/h3&gt;

&lt;p&gt;Here's the cool part: Even &lt;strong&gt;I&lt;/strong&gt; can't read your files. When you password-protect something, the encryption key is derived from your password and &lt;strong&gt;NEVER stored anywhere&lt;/strong&gt;. No password? No file. It's that simple.&lt;/p&gt;

&lt;p&gt;(This is the same tech 1Password, Bitwarden, and Signal use. If it's good enough for Edward Snowden, it's good enough for us.)&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏱️ Time Bombs
&lt;/h3&gt;

&lt;p&gt;Set files to expire in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 minutes (for the truly paranoid)&lt;/li&gt;
&lt;li&gt;24 hours (for the casually paranoid)&lt;/li&gt;
&lt;li&gt;Or custom times (for the "I know what I'm doing" folks)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👁️ View Limits
&lt;/h3&gt;

&lt;p&gt;"This file will self-destruct after 1 view."&lt;br&gt;
Or 5 views. Or 100. Your call. Once the limit hits? &lt;strong&gt;POOF.&lt;/strong&gt; File deletes itself. No takebacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Two Ways to Share
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Download a &lt;code&gt;.bar&lt;/code&gt; File&lt;/strong&gt; &lt;br&gt;
Send someone an encrypted file they can decrypt later. Good for offline sharing or when you don't trust servers (fair).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Magic Link&lt;/strong&gt;&lt;br&gt;
Share a link. We host the encrypted file and enforce the rules. Once it hits the view limit or expires? It's &lt;strong&gt;gone forever&lt;/strong&gt;. We even overwrite it 3 times with random data to make sure nobody's recovering it.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔔 Webhook Alerts (The Fun Part)
&lt;/h3&gt;

&lt;p&gt;Want to know when someone tries to access your file? Set up a webhook! Get a Discord or Slack notification when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone views your file&lt;/li&gt;
&lt;li&gt;Someone enters the wrong password&lt;/li&gt;
&lt;li&gt;Someone hits the view limit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't know about you, but getting a ping that says "⚠️ Wrong password attempt #3" is oddly satisfying.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛡️ Brute-Force Protection
&lt;/h3&gt;

&lt;p&gt;Try to guess the password? Cute. Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrong password = delays (1s, 2s, 4s, 8s...)&lt;/li&gt;
&lt;li&gt;5 wrong attempts? &lt;strong&gt;Locked out for 60 minutes.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Try to cheat by re-uploading? Nope. We track that.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hackers hate this one simple trick. (It's called "making them give up.")&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (For My Fellow Nerds 🤓)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; (because Python is still king for APIs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptography&lt;/strong&gt; library (the heavy lifter)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PBKDF2&lt;/strong&gt; for key derivation (100,000 iterations, because we're not amateurs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HMAC-SHA256&lt;/strong&gt; for tamper detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React 18&lt;/strong&gt; + &lt;strong&gt;Vite&lt;/strong&gt; (blazing fast dev experience)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; (looking good without the pain)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lucide React&lt;/strong&gt; icons (because they're clean AF)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hosting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend on &lt;strong&gt;Vercel&lt;/strong&gt; (because it just works)&lt;/li&gt;
&lt;li&gt;Backend on &lt;strong&gt;Render Free Tier&lt;/strong&gt; (warning: it takes 50 seconds to wake up from hibernation, so be patient!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Desktop Version (Plot Twist!)
&lt;/h2&gt;

&lt;p&gt;Before I built the web version, I made a &lt;strong&gt;Windows desktop app&lt;/strong&gt; (exe) that's honestly even MORE paranoid. Same core security, but with some extra spicy features:&lt;/p&gt;

&lt;h3&gt;
  
  
  🚨 Desktop-Only Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Panic Button&lt;/strong&gt;: Someone walking up behind you? Hit the button. Your files? Gone in seconds. Three destruction levels:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Selective&lt;/em&gt;: Just clear session data&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Aggressive&lt;/em&gt;: Nuke 98%+ of BAR data&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Scorched Earth&lt;/em&gt;: Maximum destruction + anti-forensics (nuclear option)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Deadman Switch&lt;/strong&gt;: Don't log in for a week? Files auto-delete themselves. &lt;em&gt;Spooky but useful.&lt;/em&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Hardware Binding&lt;/strong&gt;: Lock files to your specific PC. Try to copy them elsewhere? They won't decrypt.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;100% Offline&lt;/strong&gt;: No internet. No cloud. Your files NEVER leave your machine.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Security Levels&lt;/strong&gt;: Choose your paranoia level:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Standard&lt;/em&gt;: 5 wrong passwords = temp lockout&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;High&lt;/em&gt;: 4 wrong passwords = 24hr lockouts&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Maximum&lt;/em&gt;: 3 wrong passwords = &lt;strong&gt;EVERYTHING DELETED&lt;/strong&gt; ☠️&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Why did I make a web version then? Because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Not everyone wants a desktop app&lt;/li&gt;
&lt;li&gt;Sharing links is easier than sending .bar files&lt;/li&gt;
&lt;li&gt;I wanted to prove the same security works in a browser&lt;/li&gt;
&lt;li&gt;I wanted to flex my full-stack muscles 💪&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both versions use the same encryption standards (AES-256-GCM), so pick your poison!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🌐 Live Demo:&lt;/strong&gt; &lt;a href="https://bar-rnr.vercel.app/" rel="noopener noreferrer"&gt;https://bar-rnr.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fair warning: The backend is on a free tier that hibernates when not in use. If it's slow to load, give it ~50 seconds to wake up, stretch, and grab some coffee. After that? Lightning fast. ⚡&lt;/p&gt;

&lt;p&gt;Want to self-host? The code is on GitHub:&lt;br&gt;
&lt;strong&gt;🔗 Web Version: &lt;a href="https://github.com/Mrtracker-new/BAR_RYY" rel="noopener noreferrer"&gt;github.com/Mrtracker-new/BAR_RYY&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want the desktop app instead?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;🔗 Desktop Version (v2.0.0): &lt;a href="https://github.com/Mrtracker-new/BAR" rel="noopener noreferrer"&gt;github.com/Mrtracker-new/BAR&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The desktop version has the panic button and deadman switch—perfect for the truly paranoid! 😈&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I Learned (The Hard Way)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encryption is HARD.&lt;/strong&gt; Like, "I rewrote this 5 times" hard. Don't roll your own crypto. Use battle-tested libraries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UX matters for security tools.&lt;/strong&gt; If your security tool is annoying to use, people won't use it. Then they'll go back to emailing passwords in plaintext. 😭&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free hosting has trade-offs.&lt;/strong&gt; The 50-second wake-up time on Render? Yeah, that's the price of free. Still worth it though!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;People LOVE the webhook notifications.&lt;/strong&gt; I thought it was a silly feature. Turns out everyone wants to know when their file gets accessed. It's like having a security camera for your data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Zero-knowledge" is a great pitch.&lt;/strong&gt; Telling users "I literally CAN'T read your files" is way more reassuring than "I promise I won't read your files."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Some ideas I'm toying with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app&lt;/strong&gt; (because why not go full circle?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser extension&lt;/strong&gt; (right-click → "Share securely")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email integration&lt;/strong&gt; (auto-generate BAR links in Gmail)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiring messages&lt;/strong&gt; (not just files, but text too)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But honestly? I built this mostly because I thought it was cool. If even one person uses it to send a password securely instead of over Slack, I'll call it a win. 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Talk Section
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is this production-ready?&lt;/strong&gt; For personal use? Absolutely. For enterprise secrets? Maybe test it first. 😅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you read my files?&lt;/strong&gt; Nope! Zero-knowledge means zero-knowledge. I don't have your password, so I can't decrypt anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the server goes down?&lt;/strong&gt; If you used client-side mode (downloaded the .bar file), you're fine—it's on your machine. If you used server-side (link sharing), well... RIP. Back up important stuff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this actually secure?&lt;/strong&gt; I'm not a cryptographer, but I used industry-standard algorithms (AES-256, PBKDF2, HMAC-SHA256) implemented by people way smarter than me. The code is open source, so feel free to audit it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It, Break It, Tell Me About It
&lt;/h2&gt;

&lt;p&gt;Seriously, go play with it: &lt;strong&gt;&lt;a href="https://bar-rnr.vercel.app/" rel="noopener noreferrer"&gt;bar-rnr.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Upload a file, set it to self-destruct, feel like James Bond for 30 seconds. If you find bugs (or ways to break it), open an issue on GitHub. I accept PRs, feature requests, and memes.&lt;/p&gt;

&lt;p&gt;And if you're thinking "this is over-engineered for sharing cat pictures"—you're absolutely right. But wouldn't you rather share those cat pictures with &lt;strong&gt;military-grade encryption&lt;/strong&gt;? 😺🔐&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Made with ☕, 💻, and a healthy dose of paranoia.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - Want the desktop version with the panic button? Check out &lt;a href="https://github.com/Mrtracker-new/BAR" rel="noopener noreferrer"&gt;github.com/Mrtracker-new/BAR&lt;/a&gt; for the 100% offline, extra-paranoid version! 🚨&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Live Demo (Web):&lt;/strong&gt; &lt;a href="https://bar-rnr.vercel.app/" rel="noopener noreferrer"&gt;https://bar-rnr.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;GitHub (Web Version):&lt;/strong&gt; &lt;a href="https://github.com/Mrtracker-new/BAR_RYY" rel="noopener noreferrer"&gt;https://github.com/Mrtracker-new/BAR_RYY&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub (Desktop Version):&lt;/strong&gt; &lt;a href="https://github.com/Mrtracker-new/BAR" rel="noopener noreferrer"&gt;https://github.com/Mrtracker-new/BAR&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Questions?&lt;/strong&gt; Drop a comment below!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built LinkNest Because My Brain Is Full and My Bookmarks Are a Disaster 📲</title>
      <dc:creator>Rolan Lobo</dc:creator>
      <pubDate>Thu, 11 Dec 2025 20:27:36 +0000</pubDate>
      <link>https://forem.com/rolan_r_n_r/i-built-linknest-because-my-brain-is-full-and-my-bookmarks-are-a-disaster-1fl2</link>
      <guid>https://forem.com/rolan_r_n_r/i-built-linknest-because-my-brain-is-full-and-my-bookmarks-are-a-disaster-1fl2</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: Digital Hoarding, But Make It Chaotic
&lt;/h2&gt;

&lt;p&gt;Let me paint you a picture. You're lying in bed at 2 AM, and you find THE perfect article about why cats knock things off tables. "I'll read this tomorrow," you think, hitting that bookmark button. Fast forward three months, and you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;347 Chrome bookmarks in a folder called "Important Stuff"&lt;/li&gt;
&lt;li&gt;89 screenshots of tweets with profound wisdom&lt;/li&gt;
&lt;li&gt;PDFs scattered across Downloads, Desktop, and that one folder you created called "Organize Later"&lt;/li&gt;
&lt;li&gt;Notes in Apple Notes, Google Keep, sticky notes on your actual desk, and probably one scribbled on a napkin somewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar? Yeah, me too. That's why I built &lt;strong&gt;LinkNest&lt;/strong&gt; 🪺&lt;/p&gt;

&lt;h2&gt;
  
  
  What Even Is LinkNest?
&lt;/h2&gt;

&lt;p&gt;LinkNest is your personal knowledge vault that lives 100% on your device. No cloud sync to leak your embarrassing saved links, no subscription fees, and definitely no "We regret to inform you that your data was part of a breach" emails.&lt;/p&gt;

&lt;p&gt;Think of it as Marie Kondo for your digital life, except instead of throwing things away, you're hoarding them &lt;em&gt;efficiently&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features That Sparked Joy (at least for me)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;📎 Link Management&lt;/strong&gt;&lt;br&gt;
Save all those "read later" articles. Will you actually read them? Probably not. But at least now they're organized, and that's what matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📄 Document Storage&lt;/strong&gt;&lt;br&gt;
PDFs, images, that sourdough recipe you screenshot from Instagram at 3 AM—it all goes in the nest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📝 Quick Notes&lt;/strong&gt;&lt;br&gt;
Your brain is already juggling work deadlines, what you need from the grocery store, and every embarrassing thing you've ever said. Let LinkNest remember the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏷️ Tags, Not Folders&lt;/strong&gt;&lt;br&gt;
Because sometimes that article about Python is &lt;em&gt;also&lt;/em&gt; about productivity &lt;em&gt;and&lt;/em&gt; mental health, and folders force you to make impossible choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔍 Fast Search&lt;/strong&gt;&lt;br&gt;
Find that thing you saved months ago in milliseconds. It's like having a personal librarian, except it's just good old SQLite doing its thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔐 Privacy First (Actually First)&lt;/strong&gt;&lt;br&gt;
Your data never leaves your device. No servers, no cloud, no "we use AI to analyze your data to improve your experience" nonsense. Just pure, offline peace of mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🌙 Dark Mode&lt;/strong&gt;&lt;br&gt;
Because we're developers and our retinas have suffered enough.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Tech Stack (For My Fellow Nerds)
&lt;/h2&gt;

&lt;p&gt;I built LinkNest with Flutter because I wanted it to work on everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flutter &amp;amp; Dart&lt;/strong&gt; - Write once, run anywhere, cry occasionally when null safety gets weird&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Riverpod&lt;/strong&gt; - State management that doesn't make me want to flip a table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift&lt;/strong&gt; - SQLite, but wrapped in something that doesn't feel like I'm writing SQL in 1995&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go Router&lt;/strong&gt; - Navigation that just... works? (Is this what happiness feels like?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Fonts&lt;/strong&gt; - Because Comic Sans is never the answer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why I Built This (The Real Story)
&lt;/h2&gt;

&lt;p&gt;I'll be honest—I was tired. Tired of losing important links, tired of subscription fees for apps that do way more than I need, and really tired of wondering if my saved articles about existential dread were being used to train some company's ad algorithm.&lt;/p&gt;

&lt;p&gt;One weekend, fueled by coffee and spite, I decided to build something simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save my stuff&lt;/li&gt;
&lt;li&gt;Find my stuff&lt;/li&gt;
&lt;li&gt;Don't lose my stuff&lt;/li&gt;
&lt;li&gt;Don't send my stuff to someone else's computer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four weekends and several "why did I think this was simple?" moments later, LinkNest was born.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Best Part? It's Open Source
&lt;/h2&gt;

&lt;p&gt;Yep, the whole thing is on &lt;a href="https://github.com/Mrtracker-new/LinkNest" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use it for free (obviously)&lt;/li&gt;
&lt;li&gt;Fork it and make it better&lt;/li&gt;
&lt;li&gt;Steal my code and learn from my mistakes&lt;/li&gt;
&lt;li&gt;Submit PRs to fix things I definitely broke&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I believe knowledge should be free, and digital clutter is a universal struggle. So here we are.&lt;/p&gt;
&lt;h2&gt;
  
  
  Want to Try It?
&lt;/h2&gt;

&lt;p&gt;If you're drowning in bookmarks or just want a simple, private way to organize your digital life, give LinkNest a shot!&lt;/p&gt;
&lt;h3&gt;
  
  
  📱 Download the Android APK
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Mrtracker-new/LinkNest/releases/download/v2.0/LinkNest-v2.0.apk" rel="noopener noreferrer"&gt;⬇️ Download LinkNest v2.0 APK&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just download, install, and start organizing. No account creation, no email verification, no "please rate us in the app store" popups.&lt;/p&gt;
&lt;h3&gt;
  
  
  🛠️ Or Build It Yourself
&lt;/h3&gt;

&lt;p&gt;Prefer to build from source? No problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Mrtracker-new/LinkNest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;LinkNest
flutter pub get
flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm constantly improving LinkNest (when I'm not busy being distracted by the links I saved in LinkNest). Some ideas on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better tagging suggestions&lt;/li&gt;
&lt;li&gt;Export to markdown for all the note-taking app switchers out there&lt;/li&gt;
&lt;li&gt;More customization options&lt;/li&gt;
&lt;li&gt;Whatever wild ideas the community suggests&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building LinkNest taught me that sometimes the best solution to a problem is the simplest one. You don't need AI, blockchain, or cloud sync to organize your digital life. Sometimes you just need a good local database and the courage to say "no" to feature creep.&lt;/p&gt;

&lt;p&gt;So if you're like me—overwhelmed by digital chaos, suspicious of cloud services, and tired of subscription fatigue—maybe LinkNest is for you too.&lt;/p&gt;

&lt;p&gt;Plus, if you star the repo, it makes me feel like I'm contributing to society instead of just adding another TODO app to the internet. 😄&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Built with ❤️ and probably too much caffeine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Got questions? Found a bug? Want to tell me my UI needs work? (I know, I'm working on it)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Mrtracker-new" rel="noopener noreferrer"&gt;@Mrtracker-new&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://rolanlobo.netlify.app/" rel="noopener noreferrer"&gt;rolanlobo.netlify.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;P.S. - If you actually read all the way to the end, you're the real MVP. Most people just scroll to the code snippets.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Mrtracker-new" rel="noopener noreferrer"&gt;
        Mrtracker-new
      &lt;/a&gt; / &lt;a href="https://github.com/Mrtracker-new/LinkNest" rel="noopener noreferrer"&gt;
        LinkNest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      LinkNest is your personal knowledge vault that lives entirely on your device.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🪺 LinkNest&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Your offline vault for links, docs, and notes. No cloud, no fuss, just your stuff staying yours.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's This About?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Ever feel like you're drowning in bookmarks, random notes scattered across 47 different apps, and important documents you saved "somewhere"? Yeah, me too. That's why LinkNest exists.&lt;/p&gt;

&lt;p&gt;LinkNest is your personal knowledge vault that lives entirely on your device. No cloud sync to betray your secrets, no subscription fees to drain your wallet, no "oops we got hacked" emails. Just you, your data, and sweet, sweet privacy. 🔒&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features That'll Make You Smile&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;📎 Link Hoarding&lt;/strong&gt; - Save all those "read later" links (we both know you won't, but at least they're organized now)&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;📄 Document Storage&lt;/strong&gt; - PDFs, images, that recipe you'll totally make someday&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;📝 Quick Notes&lt;/strong&gt; - Because your brain is too full to remember everything&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;🏷️ Smart Tags&lt;/strong&gt; - Organize stuff without the…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Mrtracker-new/LinkNest" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





</description>
      <category>flutter</category>
      <category>opensource</category>
      <category>privacy</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
