<?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: ggwork</title>
    <description>The latest articles on DEV Community by ggwork (@ggwork).</description>
    <link>https://dev.to/ggwork</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%2F4070458%2Fc1960c8f-08ea-49bc-b4b7-1c0f82814c7f.jpeg</url>
      <title>DEV Community: ggwork</title>
      <link>https://dev.to/ggwork</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ggwork"/>
    <language>en</language>
    <item>
      <title>How to Build a Text Case Converter That Actually Handles Edge Cases</title>
      <dc:creator>ggwork</dc:creator>
      <pubDate>Tue, 11 Aug 2026 09:20:47 +0000</pubDate>
      <link>https://dev.to/ggwork/how-to-build-a-text-case-converter-that-actually-handles-edge-cases-3nfc</link>
      <guid>https://dev.to/ggwork/how-to-build-a-text-case-converter-that-actually-handles-edge-cases-3nfc</guid>
      <description>&lt;p&gt;While working on a collection of browser-based developer tools recently, I kept running into the same problem: I needed to convert text between different case formats constantly. camelCase for JavaScript variables, snake_case for database columns, kebab-case for CSS classes — you name it.&lt;/p&gt;

&lt;p&gt;The existing solutions? Most were either bloated web apps with more ads than functionality, or required sending my text to a server somewhere. That felt wrong for something so simple. I wanted a tool that worked entirely in the browser, fast and private.&lt;/p&gt;

&lt;p&gt;So I decided to build my own. Spoiler: it was harder than I thought. Here's what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Simple" Problem That Wasn't
&lt;/h2&gt;

&lt;p&gt;My initial mental model was straightforward. Take text, apply a bunch of string methods, display the results. How hard could it be?&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;// My first naive attempt&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?:\s&lt;/span&gt;&lt;span class="sr"&gt;|_|-&lt;/span&gt;&lt;span class="se"&gt;)(\w)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked great for "hello world". Then I tried it on real-world input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parseXMLDoc&lt;/code&gt; became &lt;code&gt;parse X M L Doc&lt;/code&gt; — terrible&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;münchen&lt;/code&gt; lost its umlaut&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Hello123World&lt;/code&gt; split in the wrong places&lt;/li&gt;
&lt;li&gt;Chinese text like &lt;code&gt;你好世界&lt;/code&gt; got mangled by the word-splitting logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's when I realized case conversion is deceptively tricky. The "simple" regex approach breaks the moment you have anything beyond basic ASCII text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unicode Awakening
&lt;/h2&gt;

&lt;p&gt;Here's what I learned: JavaScript's regular expressions have a secret weapon — Unicode property escapes. They've been around since ES2018, but most developers don't use them.&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;// Split words properly, keeping Unicode characters intact&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;splitWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;([\p&lt;/span&gt;&lt;span class="sr"&gt;{Ll}&lt;/span&gt;&lt;span class="se"&gt;\p&lt;/span&gt;&lt;span class="sr"&gt;{N}&lt;/span&gt;&lt;span class="se"&gt;])(\p&lt;/span&gt;&lt;span class="sr"&gt;{Lu}&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1 $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// helloWorld -&amp;gt; hello World&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\p&lt;/span&gt;&lt;span class="sr"&gt;{Lu}+&lt;/span&gt;&lt;span class="se"&gt;)(\p&lt;/span&gt;&lt;span class="sr"&gt;{Lu}&lt;/span&gt;&lt;span class="se"&gt;\p&lt;/span&gt;&lt;span class="sr"&gt;{Ll}&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1 $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// parseXMLDoc -&amp;gt; parse XML Doc&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;_&lt;/span&gt;&lt;span class="se"&gt;\-\s]&lt;/span&gt;&lt;span class="sr"&gt;+/gu&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="c1"&gt;// snake_case -&amp;gt; snake case&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;\p{L}&lt;/code&gt; and &lt;code&gt;\p{Lu}&lt;/code&gt; patterns match any Unicode letter (lowercase and uppercase respectively), not just ASCII. The &lt;code&gt;u&lt;/code&gt; flag enables Unicode mode. This single change fixed most of my edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Collaboration
&lt;/h2&gt;

&lt;p&gt;I built this tool with heavy AI assistance, and it was a mix of impressive and frustrating. Here's how it went:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where AI excelled:&lt;/strong&gt; The initial structure, i18n setup, and basic conversion functions came together quickly. I described what I needed and got a solid foundation in minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where AI struggled:&lt;/strong&gt; Edge cases. My first prompt produced code that handled "hello world" perfectly but fell apart on real-world input. The AI didn't think about Unicode, didn't consider how &lt;code&gt;parseXMLDoc&lt;/code&gt; should split, and assumed all text was ASCII.&lt;/p&gt;

&lt;p&gt;The back-and-forth was revealing. I'd test with weird input, find a bug, describe it to the AI, and get a fix. But the fixes were often band-aids — patching one edge case without considering the broader pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My honest take:&lt;/strong&gt; AI is excellent for scaffolding and common patterns, but you still need to think critically about your domain. The AI didn't know that case conversion involves Unicode normalization, acronym handling, and locale-specific rules. I had to bring that knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The i18n Decision
&lt;/h2&gt;

&lt;p&gt;One thing I insisted on from the start: full internationalization. The tool targets both Chinese and English speakers, and I wanted it done right.&lt;/p&gt;

&lt;p&gt;The approach was simple but effective:&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;I18N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Case Converter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Copy&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectLang&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh&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;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&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;p&gt;Language detection follows a sensible priority: URL parameter overrides everything, then browser language, then defaults to Chinese. It's not perfect — some users might prefer English even with a Chinese browser — but it covers most cases.&lt;/p&gt;

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

&lt;p&gt;Here's something I didn't expect: the copy button was the hardest part to get 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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;copyText&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&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="nf"&gt;showFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copied&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;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback for older browsers&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textarea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textarea&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textarea&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;navigator.clipboard&lt;/code&gt; API is clean but requires HTTPS and secure context. The fallback uses the old &lt;code&gt;execCommand&lt;/code&gt; approach, which works everywhere but feels hacky. And in both cases, you need to handle the user feedback — showing "Copied!" vs "Copy failed" — which adds UI complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;With real-time preview, performance matters. Every keystroke triggers ten different conversions. For normal text, this is instant. But what about pasting a 10MB document?&lt;/p&gt;

&lt;p&gt;The solution was surprisingly simple: debounce the input.&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;let&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&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;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&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="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="nx"&gt;updateResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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;p&gt;A 100ms debounce means the UI stays responsive even with large inputs. The conversions themselves are O(n) string operations, so they're fast — it's the DOM updates that slow things down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;Looking back, there are a few things I'd change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test with real-world data from the start.&lt;/strong&gt; I should have created a test suite with edge cases before writing the conversion logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider locale-specific rules.&lt;/strong&gt; Turkish has a dotted/dotless I that behaves differently from English. German has ß which converts to SS in uppercase. These are niche but real.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build the i18n system first.&lt;/strong&gt; I added it later, which meant retrofitting text throughout the codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;The final tool does what I needed: instant case conversion in the browser, no data leaving the device, supporting ten different formats. It handles Unicode gracefully, works in dark mode, and adapts to both Chinese and English users.&lt;/p&gt;

&lt;p&gt;The full implementation lives at &lt;a href="https://craftvo.app/en/tool/case-converter" rel="noopener noreferrer"&gt;craftvo.app&lt;/a&gt; if you want to see it in action.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unicode property escapes are your friend.&lt;/strong&gt; &lt;code&gt;\p{L}&lt;/code&gt; and friends make JavaScript regex way more powerful for international text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI assistance has limits.&lt;/strong&gt; It's great for getting started, but you need domain knowledge to catch the edge cases it misses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time features need debouncing.&lt;/strong&gt; Even fast operations can lag with large inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internationalization isn't optional.&lt;/strong&gt; Your users speak more languages than you think.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building this tool taught me that even "simple" text operations have hidden complexity. The next time you reach for &lt;code&gt;text.toUpperCase()&lt;/code&gt;, remember: there's a whole world of edge cases lurking beneath that simple method call.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Real-Time Base Converter: The Hidden Complexity of a "Simple" Tool</title>
      <dc:creator>ggwork</dc:creator>
      <pubDate>Tue, 11 Aug 2026 01:26:36 +0000</pubDate>
      <link>https://dev.to/ggwork/building-a-real-time-base-converter-the-hidden-complexity-of-a-simple-tool-3fae</link>
      <guid>https://dev.to/ggwork/building-a-real-time-base-converter-the-hidden-complexity-of-a-simple-tool-3fae</guid>
      <description>&lt;h2&gt;
  
  
  Article
&lt;/h2&gt;

&lt;p&gt;You know those moments when you think a task is going to take 20 minutes, and then you're still debugging edge cases four hours later? That was me with a base converter. I mean, how hard can it be? It's just &lt;code&gt;parseInt()&lt;/code&gt; and &lt;code&gt;.toString()&lt;/code&gt;, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler: it's never just that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While working on a collection of browser-based developer tools, I needed a base converter that could handle binary, octal, decimal, and hexadecimal conversions in real-time. I thought I'd have it done before my coffee got cold. Instead, I learned more about JavaScript number limitations, BigInt quirks, and input validation than I ever wanted to know.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Simple" Problem That Wasn't
&lt;/h2&gt;

&lt;p&gt;The core requirement sounded straightforward: build a tool where you type a number in any base and see it instantly converted to all other bases. Type &lt;code&gt;ff&lt;/code&gt; in hexadecimal, and you should see &lt;code&gt;255&lt;/code&gt; in decimal, &lt;code&gt;377&lt;/code&gt; in octal, and &lt;code&gt;11111111&lt;/code&gt; in binary — all updating as you type.&lt;/p&gt;

&lt;p&gt;The first version took about 15 minutes. It was beautifully naive:&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;function&lt;/span&gt; &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toBase&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;p&gt;Three lines. What could possibly go wrong?&lt;/p&gt;




&lt;h2&gt;
  
  
  When JavaScript Numbers Betray You
&lt;/h2&gt;

&lt;p&gt;The first bug appeared with numbers that should have worked fine. I typed &lt;code&gt;99999999999999999999&lt;/code&gt; in decimal and watched the tool return complete garbage in binary. Not even close to correct — just nonsense.&lt;/p&gt;

&lt;p&gt;Here's the thing: JavaScript numbers are IEEE 754 double-precision floating point. They can only precisely represent integers up to &lt;code&gt;2^53 - 1&lt;/code&gt; (about 9 quadrillion). Anything larger loses precision silently. No error, no warning — just wrong answers.&lt;/p&gt;

&lt;p&gt;The solution was BigInt, but it came with its own set of headaches. You can't just swap &lt;code&gt;parseInt()&lt;/code&gt; for &lt;code&gt;BigInt()&lt;/code&gt; and call it a day. The API is different, you need explicit conversion functions, and you have to handle the fact that &lt;code&gt;BigInt&lt;/code&gt; doesn't work with &lt;code&gt;Math&lt;/code&gt; functions or the &lt;code&gt;toString()&lt;/code&gt; method in the same way.&lt;/p&gt;

&lt;p&gt;After some iteration, I ended up with something like this:&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;function&lt;/span&gt; &lt;span class="nf"&gt;convertValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toBase&lt;/span&gt;&lt;span class="p"&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;value&lt;/span&gt; &lt;span class="o"&gt;===&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bigIntValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseToBigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bigIntValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toBase&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;p&gt;The key insight was creating a custom &lt;code&gt;parseToBigInt&lt;/code&gt; function that handles the base validation and character checking before conversion. This way, I could catch invalid input early and show a friendly error instead of silently producing garbage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Validation Rabbit Hole
&lt;/h2&gt;

&lt;p&gt;This is where the project really started to consume my day. Each base has its own set of valid characters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binary: only &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Octal: &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;7&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Decimal: &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hexadecimal: &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;9&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt; through &lt;code&gt;f&lt;/code&gt; (case-insensitive)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seems simple, but users will type anything. I've seen people try to enter &lt;code&gt;2&lt;/code&gt; in binary, &lt;code&gt;8&lt;/code&gt; in octal, and once someone tried to enter &lt;code&gt;g&lt;/code&gt; in hex — which is technically valid for base 17, but definitely not for base 16.&lt;/p&gt;

&lt;p&gt;The validation logic ended up being the most important part of the whole tool. Without it, you get silent failures and confusing results. With it, you get a clear error message and a red border on the offending input.&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;function&lt;/span&gt; &lt;span class="nf"&gt;validateInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validChars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0123456789abcdefghijklmnopqrstuvwxyz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;validChars&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]+$`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i&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="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;p&gt;This single function prevented more confusion than everything else combined. The moment someone types an invalid character, they see exactly what's wrong instead of watching numbers transform into something unrecognizable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Custom Base Feature That Almost Got Cut
&lt;/h2&gt;

&lt;p&gt;The requirements called for supporting custom bases from 2 to 36. I initially thought this would be trivial — just make the base a variable instead of a constant. But the implementation had a subtle complexity: the custom base input itself needed to be a number input, while the value input needed to be validated against that base.&lt;/p&gt;

&lt;p&gt;The tricky part was handling the interaction between the two. If someone changes the base from 16 to 2 while there's a &lt;code&gt;ff&lt;/code&gt; in the custom value field, what should happen? I decided to clear the custom value and show a message that the value is invalid for the new base. It's not the most elegant solution, but it's honest and doesn't pretend to do something it can't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real-Time Conversion Architecture
&lt;/h2&gt;

&lt;p&gt;The core challenge was making all conversions happen instantly without creating infinite loops or performance issues. Here's the pattern I settled on:&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;let&lt;/span&gt; &lt;span class="nx"&gt;isUpdating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&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;isUpdating&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="nx"&gt;isUpdating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBaseFromId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;markInvalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;clearConversions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;isUpdating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bigIntValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseToBigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;updateAllFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bigIntValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;updateBitInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bigIntValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;isUpdating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;p&gt;The &lt;code&gt;isUpdating&lt;/code&gt; flag was crucial. Without it, updating one field would trigger updates in others, which would trigger updates back, creating an infinite loop. This is a classic problem in any real-time synchronized form, and the boolean guard is the simplest solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AI Actually Helped (and Where It Didn't)
&lt;/h2&gt;

&lt;p&gt;I used AI assistance for this project, and it was a mixed bag — which I think is the honest truth about AI-assisted development right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What AI handled well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating the initial HTML structure with all the input fields and buttons&lt;/li&gt;
&lt;li&gt;Writing the i18n mechanism for Chinese/English support&lt;/li&gt;
&lt;li&gt;Creating the responsive CSS with dark mode support&lt;/li&gt;
&lt;li&gt;Implementing the copy-to-clipboard functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI was particularly good at boilerplate and structure. I could describe what I wanted in plain language and get a functional skeleton in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where AI struggled:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first version of the conversion logic was completely broken for large numbers. The AI used &lt;code&gt;parseInt()&lt;/code&gt; without any consideration for precision issues. When I pointed out the problem, it suggested using &lt;code&gt;BigInt&lt;/code&gt; but then made a mess of the conversion functions, mixing &lt;code&gt;BigInt&lt;/code&gt; and regular number operations in ways that threw type errors.&lt;/p&gt;

&lt;p&gt;The validation logic also took several iterations. The AI initially used simple character checks that didn't properly account for case-insensitivity or the full range of valid characters in higher bases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; AI is great for scaffolding and structure, but you still need to understand the domain deeply enough to catch its mistakes. In this case, the numerical edge cases and validation logic required genuine understanding of both JavaScript's limitations and the mathematical requirements of base conversion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;One thing that surprised me was how easy it was to make this performant. The conversions themselves are trivial — even with BigInt, converting a 100-digit number takes microseconds. The real performance concern was DOM updates.&lt;/p&gt;

&lt;p&gt;The solution was to batch all DOM updates together and only update what actually changed. Instead of updating every field on every keystroke, I compare the new value with the current value and only update if they're different:&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;function&lt;/span&gt; &lt;span class="nf"&gt;updateField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tiny optimization prevents unnecessary DOM mutations and keeps the tool responsive even when pasting in a massive number.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bit Operation Feature
&lt;/h2&gt;

&lt;p&gt;The requirements included showing bitwise operations (AND, OR, XOR, NOT, left shift, right shift). This was the feature I almost cut because it seemed unnecessary. But it turned out to be the most interesting part.&lt;/p&gt;

&lt;p&gt;The challenge was that JavaScript's bitwise operators work on 32-bit integers, which meant I had to use &lt;code&gt;BigInt&lt;/code&gt; for the bit operations too:&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;function&lt;/span&gt; &lt;span class="nf"&gt;showBitOperations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bigInt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;not&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nx"&gt;bigInt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;leftShift1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigInt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rightShift1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigInt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;n&lt;/code&gt; suffix on numeric literals for BigInt was a subtle gotcha that took me a minute to remember. Small things like that are where AI assistance actually helped — it caught the missing &lt;code&gt;n&lt;/code&gt; before I did.&lt;/p&gt;




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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never trust JavaScript number precision&lt;/strong&gt; — always use BigInt for anything that could exceed &lt;code&gt;2^53&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation is the most important feature&lt;/strong&gt; — a tool that gives wrong answers silently is worse than one that refuses to work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The infinite loop guard is non-negotiable&lt;/strong&gt; — any real-time synchronized form needs a reentrancy guard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI assistance is a multiplier, not a replacement&lt;/strong&gt; — it made me 3x faster, but I still needed to understand every line it generated&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;After all that, I ended up with a tool that does exactly what I originally wanted: type a number in any base and see it instantly converted everywhere else, with bit operations and byte size displayed below. It's fast, handles huge numbers correctly, and gives clear errors when you type something invalid.&lt;/p&gt;

&lt;p&gt;During this process, I built a small browser-based tool to make this workflow easier. If you're interested, you can check it out &lt;a href="https://craftvo.app/en/tool/base-converter" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next time you use a base converter, spare a thought for the poor developer who had to handle BigInt edge cases and validation regexes. It's never as simple as it looks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;javascript&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;webdev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;programming&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tutorial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tooling&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>debugging</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to Build a Browser-Based YAML Parser Without a Backend</title>
      <dc:creator>ggwork</dc:creator>
      <pubDate>Mon, 10 Aug 2026 06:10:22 +0000</pubDate>
      <link>https://dev.to/ggwork/how-to-build-a-browser-based-yaml-parser-without-a-backend-1me0</link>
      <guid>https://dev.to/ggwork/how-to-build-a-browser-based-yaml-parser-without-a-backend-1me0</guid>
      <description>&lt;h1&gt;
  
  
  How to Build a Browser-Based YAML Parser Without a Backend
&lt;/h1&gt;

&lt;p&gt;Last month, I was debugging a Kubernetes configuration file at 11 PM. The YAML was valid — or so I thought — but something was off. I couldn't tell if it was an indentation issue, a misquoted string, or a subtle anchor reference gone wrong. I opened an online YAML parser, pasted my config, and waited... for my data to upload to some server I didn't trust.&lt;/p&gt;

&lt;p&gt;That's when it hit me: why does a simple parsing tool need a backend at all?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Existing Solutions
&lt;/h2&gt;

&lt;p&gt;Most online YAML tools have the same problem. They're either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-side parsers&lt;/strong&gt; that upload your data to unknown infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy CLI tools&lt;/strong&gt; that require installation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-engineered SaaS platforms&lt;/strong&gt; with accounts and pricing pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a developer who just wants to validate a config file, none of these feel right. I needed something that ran entirely in the browser, respected privacy, and didn't require me to sign up for anything.&lt;/p&gt;

&lt;p&gt;So I built one. Here's how it went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Library
&lt;/h2&gt;

&lt;p&gt;The first decision was the most obvious one: which YAML parser to use?&lt;/p&gt;

&lt;p&gt;I could have written my own parser. I've done it before — for JSON, for CSV, even for a custom query language I invented in a moment of questionable judgment. Writing a YAML parser from scratch is a rabbit hole of edge cases: anchors, aliases, multi-line strings, flow collections, type coercion...&lt;/p&gt;

&lt;p&gt;The YAML spec is deceptively complex. I value my sanity.&lt;/p&gt;

&lt;p&gt;The real choice was between &lt;code&gt;js-yaml&lt;/code&gt; and &lt;code&gt;yaml&lt;/code&gt; (the newer package). Both are solid. But &lt;code&gt;js-yaml&lt;/code&gt; has been around longer, has better documentation, and — critically for a browser tool — has a single-file CDN build that just works.&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;// The core of the whole tool — everything else is UI&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsyaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the magic line. Everything else — the tree view, the error handling, the formatting — is just presentation around this one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Keeping It Simple
&lt;/h2&gt;

&lt;p&gt;I'm a firm believer in not over-engineering tools. This project needed to be a single HTML file with inline CSS and JavaScript. No build step, no package manager, no framework.&lt;/p&gt;

&lt;p&gt;Why? Because browser-based developer tools should be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shareable&lt;/strong&gt; — you can send one file to anyone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployable&lt;/strong&gt; — drop it on any static host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debuggable&lt;/strong&gt; — open DevTools and see everything&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The layout is straightforward: a toolbar on top, two panels side by side (input on the left, output on the right), and a status bar at the bottom.&lt;/p&gt;

&lt;p&gt;The tricky part was the tree view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Tree View: A Lesson in Recursion
&lt;/h2&gt;

&lt;p&gt;The tree view was the feature I underestimated. Converting parsed YAML (which is just nested JavaScript objects and arrays) into a collapsible tree sounds simple. But there are subtleties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do you distinguish between an empty object and an empty string?&lt;/li&gt;
&lt;li&gt;What about &lt;code&gt;null&lt;/code&gt; vs &lt;code&gt;undefined&lt;/code&gt; vs missing keys?&lt;/li&gt;
&lt;li&gt;How do you handle circular references? (Spoiler: YAML can actually produce these with anchors)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&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="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;data&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;span class="tree-null"&amp;gt;null&amp;lt;/span&amp;gt;&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ... recursively render children&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;span class="tree-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;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;p&gt;The type-based coloring was a nice touch — strings in green, numbers in purple, booleans in red. It makes scanning a complex config much faster than reading raw text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling: The Part Nobody Gets Right
&lt;/h2&gt;

&lt;p&gt;Here's where most YAML tools fail: they crash or show a cryptic message when parsing fails.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;js-yaml&lt;/code&gt; gives you a &lt;code&gt;mark&lt;/code&gt; object with line and column information. But the error messages are... let's say, developer-focused. A typical error looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;unexpected end of the stream within a double quoted scalar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not helpful if you just want to know what line is broken.&lt;/p&gt;

&lt;p&gt;I wrapped the error handling to extract position information and display it clearly:&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsyaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// success path&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// show "Error at line X, column Y" instead of raw message&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was one of those "small features that make a huge difference" moments. The difference between "your YAML is broken" and "line 42, column 8 is where things go wrong" is the difference between frustration and a quick fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The i18n Surprise
&lt;/h2&gt;

&lt;p&gt;I decided to support both Chinese and English from the start. The tool's audience is developers, and a huge portion of developer content is consumed by non-English speakers.&lt;/p&gt;

&lt;p&gt;The implementation was simple — a &lt;code&gt;t()&lt;/code&gt; function that looks up keys in a dictionary:&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;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YAML 解析器&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parse&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;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YAML Parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Parse&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentLang&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But here's what surprised me: &lt;strong&gt;the i18n requirement forced me to write better code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because every user-facing string had to go through &lt;code&gt;t()&lt;/code&gt;, I couldn't hardcode anything. This meant I had to think carefully about every message, every label, every button. The structure became cleaner because the strings were defined in one place.&lt;/p&gt;

&lt;p&gt;If you're building a tool and think "I'll add i18n later" — don't. It's easier to do it from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Development: The Honest Take
&lt;/h2&gt;

&lt;p&gt;Now for the part I know you're curious about. I built this with AI assistance, and I want to be honest about what that looked like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went well:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI was excellent at generating the initial structure. I described the layout, the features, and the design tokens, and it produced a working skeleton in minutes. The CSS variables, the dark mode support, the responsive grid — all generated in the first pass.&lt;/p&gt;

&lt;p&gt;The i18n dictionary was also AI-generated. I gave it a list of strings and asked for translations, and it produced accurate, natural-sounding translations in both languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI's first attempt at the tree view was a mess. It used nested tables (tables! in 2024!) and had no concept of type-based coloring. I had to rewrite that component entirely.&lt;/p&gt;

&lt;p&gt;The error handling was also initially naive. The AI just displayed &lt;code&gt;e.message&lt;/code&gt; from js-yaml, which produces those cryptic errors I mentioned. I had to explicitly prompt it to extract the line/column information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow that worked:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I treated the AI like a junior developer. I gave it clear, specific instructions and reviewed everything it produced. When something was wrong, I didn't just say "fix this" — I explained &lt;em&gt;why&lt;/em&gt; it was wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The tree view needs to show types with colors. Strings are green, numbers are purple. Also, the toggle arrows should be clickable to expand/collapse."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This back-and-forth worked surprisingly well. The AI got better with each iteration, and by the end, it was producing code that needed minimal review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My honest take:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI-assisted coding is like having a very fast typist who sometimes misunderstands the assignment. It's incredibly productive if you're a good reviewer. It's a disaster if you just accept everything.&lt;/p&gt;

&lt;p&gt;For a project like this — a self-contained tool with clear requirements — AI assistance probably saved me 40% of the time. But that time savings came with a cost: I had to be more careful reviewing edge cases and error paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;One thing I learned from previous tools: don't parse on every keystroke.&lt;/p&gt;

&lt;p&gt;I added a debounce to the input handler — 300ms after the user stops typing, then parse. This makes the tool feel instant for small files but doesn't choke on large configs.&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;let&lt;/span&gt; &lt;span class="nx"&gt;debounceTimer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&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;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&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;debounceTimer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;debounceTimer&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="nx"&gt;parseAndRender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&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;p&gt;For the file upload, I read the file as text and dump it into the textarea. The browser handles the rest. No need for complex file handling when a textarea does the job.&lt;/p&gt;

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

&lt;p&gt;Building this tool taught me a few things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The browser is a legitimate runtime for developer tools.&lt;/strong&gt; With libraries like &lt;code&gt;js-yaml&lt;/code&gt; available via CDN, there's almost no reason to have a backend for parsing and transformation tools. The privacy benefit is a feature worth marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Error messages are a feature, not an afterthought.&lt;/strong&gt; The difference between "error at line 3" and "unexpected token" is the difference between a tool you use daily and one you bookmark and forget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. AI assistance requires strong fundamentals.&lt;/strong&gt; I could only effectively direct the AI because I knew what good code looked like. The AI didn't teach me recursion or event handling — I had to know that already.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Tools should be self-contained.&lt;/strong&gt; The single-file HTML approach means this tool can be hosted anywhere, shared as a file, or even run locally by double-clicking. That's the kind of portability developers appreciate.&lt;/p&gt;

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

&lt;p&gt;During this process, I built a small browser-based tool to make this workflow easier. It handles YAML parsing, validation, JSON conversion, and tree-style visualization — all without sending a single byte to a server.&lt;/p&gt;

&lt;p&gt;If you work with YAML files for Docker, Kubernetes, or CI/CD pipelines, it might save you a few minutes of frustration.&lt;/p&gt;

&lt;p&gt;You can try it here: &lt;a href="https://craftvo.app/en/tool/yaml-parser" rel="noopener noreferrer"&gt;YAML Parser&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you built your own developer tools? What was your experience with AI-assisted coding? Let me know in the comments — I'm genuinely curious how other devs are navigating this new workflow.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;javascript&lt;/code&gt; &lt;code&gt;yaml&lt;/code&gt; &lt;code&gt;webdev&lt;/code&gt; &lt;code&gt;tooling&lt;/code&gt; &lt;code&gt;ai&lt;/code&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
