<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: zhen</title>
    <description>The latest articles on DEV Community by zhen (@nehznah).</description>
    <link>https://dev.to/nehznah</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155567%2Fb430df9e-3426-4d80-b0c8-a77f07085970.jpg</url>
      <title>DEV Community: zhen</title>
      <link>https://dev.to/nehznah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nehznah"/>
    <language>en</language>
    <item>
      <title>Capturing custom dropdowns is harder than you think: lessons from building a form recovery extension</title>
      <dc:creator>zhen</dc:creator>
      <pubDate>Fri, 09 Oct 2026 18:05:01 +0000</pubDate>
      <link>https://dev.to/nehznah/capturing-custom-dropdowns-is-harder-than-you-think-lessons-from-building-a-form-recovery-extension-4ha2</link>
      <guid>https://dev.to/nehznah/capturing-custom-dropdowns-is-harder-than-you-think-lessons-from-building-a-form-recovery-extension-4ha2</guid>
      <description>&lt;p&gt;Native &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements are easy. You read &lt;code&gt;.value&lt;/code&gt;, you set &lt;code&gt;.value&lt;/code&gt;, done. The problem is that half the web doesn't use native selects anymore.&lt;/p&gt;

&lt;p&gt;Every UI framework has its own dropdown: React Select, MUI Autocomplete, Ant Design Select, custom-built divs with ARIA roles. They look like dropdowns, they act like dropdowns, but under the hood they're divs, spans, and JavaScript state that the DOM doesn't know about.&lt;/p&gt;

&lt;p&gt;I ran into this building FormSafe, a Chrome extension that saves what you type in forms so you can restore it after a tab crash. Text inputs were straightforward. Custom dropdowns nearly broke the whole project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why custom dropdowns are hard to capture
&lt;/h2&gt;

&lt;p&gt;A native select stores its state in the DOM. A custom dropdown stores its state in JavaScript. When you need to save "what did the user pick," you're asking two different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What does the DOM show?&lt;/strong&gt; (the visible label, e.g. "California")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does the app think is selected?&lt;/strong&gt; (the internal value, e.g. &lt;code&gt;"CA"&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For restoration, you need #2. But #2 lives in a closure, a React state hook, a Vue ref — somewhere your content script can't reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Read the visible text (fragile)
&lt;/h2&gt;

&lt;p&gt;The naive approach: find the element that displays the selected value, save its &lt;code&gt;textContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This breaks immediately. The visible text is "California" but the form expects &lt;code&gt;"CA"&lt;/code&gt;. On restore, you set the text to "California" and the app's internal state still says nothing is selected. Submit the form and the server gets an empty value.&lt;/p&gt;

&lt;p&gt;Worse: some dropdowns show different text than the value (e.g. displaying "CA — California" but storing &lt;code&gt;"CA"&lt;/code&gt;). Text matching is a guessing game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Simulate the clicks (slow but honest)
&lt;/h2&gt;

&lt;p&gt;What actually works: on restore, programmatically reopen the dropdown and click the right option, the same way a user would.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Find the dropdown trigger element (the thing the user clicks to open it)&lt;/li&gt;
&lt;li&gt;Dispatch a real &lt;code&gt;click&lt;/code&gt; event on it&lt;/li&gt;
&lt;li&gt;Wait for the options list to render (MutationObserver, with a timeout fallback)&lt;/li&gt;
&lt;li&gt;Find the option matching the saved value&lt;/li&gt;
&lt;li&gt;Dispatch &lt;code&gt;click&lt;/code&gt; on that option&lt;/li&gt;
&lt;li&gt;Verify the selection took effect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works because you're going through the same code path as a real user interaction. The framework's internal state updates correctly because as far as it knows, someone actually clicked.&lt;/p&gt;

&lt;p&gt;The downside: it's slow. Each dropdown takes 200-500ms to restore (open, wait, click, verify). A form with 10 custom dropdowns takes a few seconds. Acceptable for a recovery flow, terrible for anything real-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Framework-specific hooks (powerful, unmaintainable)
&lt;/h2&gt;

&lt;p&gt;You can reach into React's fiber tree, or Vue's component instance, and read/write state directly. I've seen extensions do this.&lt;/p&gt;

&lt;p&gt;I decided against it. Every framework version changes internals. You'd be maintaining a compatibility matrix instead of building features. The click-simulation approach is framework-agnostic — it doesn't care if it's React 18, Vue 3, or vanilla JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The edge cases that keep me up at night
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Async option loading.&lt;/strong&gt; Some dropdowns fetch options from an API when opened. On restore, you open the dropdown, the options aren't there yet, your MutationObserver fires on the loading spinner. You need to wait for the actual options, with a sane timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-select with chips.&lt;/strong&gt; The selected values render as removable chips. Restoring means clicking each option individually, then verifying the chip count matches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependent dropdowns.&lt;/strong&gt; Country → State → City. You must restore them in order, waiting for each to populate the next. Get the order wrong and you're selecting "California" in a city dropdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtualized lists.&lt;/strong&gt; Long option lists only render visible items. The option you need might not be in the DOM until you scroll. This one I still don't handle perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this today
&lt;/h2&gt;

&lt;p&gt;Don't try to be clever. Simulate user interactions. It's slower and less elegant than reaching into framework internals, but it works across every framework and doesn't break when React ships a new version.&lt;/p&gt;

&lt;p&gt;And be honest about failures. If a dropdown can't be restored reliably, tell the user which one. Silently filling in wrong data is worse than admitting a gap.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is from building FormSafe, a free Chrome extension for form recovery. The dropdown problem was the hardest part by far — text inputs took a day, dropdowns took two weeks.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Silence is distracting. Music is distracting. So I built a third option.</title>
      <dc:creator>zhen</dc:creator>
      <pubDate>Fri, 09 Oct 2026 17:28:22 +0000</pubDate>
      <link>https://dev.to/nehznah/silence-is-distracting-music-is-distracting-so-i-built-a-third-option-2i58</link>
      <guid>https://dev.to/nehznah/silence-is-distracting-music-is-distracting-so-i-built-a-third-option-2i58</guid>
      <description>&lt;p&gt;I work from home. Complete silence makes my brain invent problems to worry about. Music with lyrics hijacks the language part of my brain — I'll be halfway through a function and realize I've been mentally singing along for five minutes.&lt;/p&gt;

&lt;p&gt;There's actual research on this. A 2012 study found that moderate ambient noise — around 70 decibels, roughly coffee shop level — boosts creative thinking compared to both silence and loud environments. The theory is that a little bit of distraction forces your brain to work slightly harder to focus, and that extra effort spills over into more abstract thinking.&lt;/p&gt;

&lt;p&gt;So the science says: don't work in silence, don't blast music, sit in a coffee shop. Great. Except I can't work from a coffee shop every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried before building
&lt;/h2&gt;

&lt;p&gt;YouTube "10 hours of rain sounds" videos. They work until an ad for car insurance shatters your flow state at hour 3. YouTube Premium would fix it but I'm not paying monthly to listen to rain.&lt;/p&gt;

&lt;p&gt;Noisli, myNoise, the usual suspects. Some are good. Most want a subscription for the full sound library, or they're web-only which means keeping a tab open, or the mobile app is a completely separate purchase.&lt;/p&gt;

&lt;p&gt;Spotify focus playlists. They all start sounding the same after a week, and eventually the algorithm decides I want lo-fi hip hop with the exact same four chords on loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually wanted
&lt;/h2&gt;

&lt;p&gt;Something stupidly simple: pick a few sounds, set their volumes, forget about it. Rain a little louder than the cafe chatter. Brown noise underneath everything. No account. No monthly fee. No tab I have to keep open.&lt;/p&gt;

&lt;p&gt;So I built Drift. Chrome extension, sits in the background:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12 natural sounds.&lt;/strong&gt; Rain, thunder, cafe, forest, fireplace, brown/pink/white noise, that kind of thing. Each with its own volume slider, so you're actually mixing, not just picking a preset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runs without a tab.&lt;/strong&gt; It's a proper extension, not a website pretending to be an app. Close everything, the sound keeps going.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No account, no subscription.&lt;/strong&gt; I built this for myself. I'm not building a SaaS around rain sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mix that works for me
&lt;/h2&gt;

&lt;p&gt;Rain at 70%, distant thunder at 20%, brown noise at 30% underneath. Loud enough to fill the silence, quiet enough that I forget it's there after ten minutes. That's the sweet spot — if you're consciously noticing the sound, it's too loud.&lt;/p&gt;

&lt;p&gt;Everyone's different though. Some people swear by cafe + fireplace. My friend does forest + rain and claims it's the only way he can write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's free. If the default mixes don't work for you, play with the sliders — that's the whole point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/ebjeemplggkkajppfogpokmbpkkmchee" rel="noopener noreferrer"&gt;Get Drift on the Chrome Web Store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious what combos other people land on. The "right" background noise seems weirdly personal.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your browser doesn't save what you type. I learned that twice in one month.</title>
      <dc:creator>zhen</dc:creator>
      <pubDate>Wed, 07 Oct 2026 16:39:29 +0000</pubDate>
      <link>https://dev.to/nehznah/i-lost-a-40-minute-job-application-to-a-tab-crash-so-i-built-a-fix-32cd</link>
      <guid>https://dev.to/nehznah/i-lost-a-40-minute-job-application-to-a-tab-crash-so-i-built-a-fix-32cd</guid>
      <description>&lt;p&gt;The second time, I just sat there staring at an empty form for a full minute. Forty minutes of job application — work history, cover letter, those miserable "describe a time you demonstrated leadership" essays — gone because a tab crashed.&lt;/p&gt;

&lt;p&gt;Session restore brought back the URL. Not a single word I typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing nobody tells you
&lt;/h2&gt;

&lt;p&gt;Form data lives in the DOM. When the renderer process dies, your input dies with it. The browser has no idea what you wrote. It never did.&lt;/p&gt;

&lt;p&gt;"But autocomplete—" No. Autocomplete knows your name and email. It doesn't know about the paragraph you just wrote explaining why you're a good fit for a job.&lt;/p&gt;

&lt;p&gt;"But the site should autosave—" Some do. Google Docs does. Your bank's loan application? The government portal? The janky ATS that 90% of companies use? Absolutely not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazarus is dead and nothing replaced it
&lt;/h2&gt;

&lt;p&gt;If you've been around, you remember Lazarus Form Recovery. Massive user base, did exactly this. Got pulled from the Chrome Web Store years ago and never came back.&lt;/p&gt;

&lt;p&gt;I looked at what's out there now. Abandoned projects. Or worse — tools that read everything you type and phone home to someone's server. For a form recovery tool, that's a hard no from me.&lt;/p&gt;

&lt;p&gt;So I built my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  FormSafe: what I built and the calls I'd defend
&lt;/h2&gt;

&lt;p&gt;Chrome extension. Watches what you type in forms, saves it locally as you go. Tab crashes, you click the icon, hit restore, done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;100% local.&lt;/strong&gt; No server, no account, no analytics. I don't want your half-written job applications and you shouldn't want mine. The tradeoff: no cross-device sync. I'm fine with that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passwords are never saved.&lt;/strong&gt; Not a toggle, not a setting. The code just doesn't do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30-day auto-expiry.&lt;/strong&gt; Otherwise it becomes a graveyard of drafts you'll never open again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest about failures.&lt;/strong&gt; This one's important. Some custom JS dropdowns are genuinely hard to capture. If a field can't be restored reliably, FormSafe tells you which one — instead of silently filling in wrong data and letting you submit a broken application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, break it, tell me
&lt;/h2&gt;

&lt;p&gt;It's free. No account, no catch. I built it because I needed it, and I'm still finding edge cases — every site builds form widgets differently.&lt;/p&gt;

&lt;p&gt;If something doesn't restore right, I want to hear about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/deklfkdfegechjgmpjinlghcllgojhni" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/deklfkdfegechjgmpjinlghcllgojhni&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>chrome</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
