<?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: hashem alharhashi</title>
    <description>The latest articles on DEV Community by hashem alharhashi (@hashem_alharhashi_3777a84).</description>
    <link>https://dev.to/hashem_alharhashi_3777a84</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%2F4074715%2Fd022af9f-daab-41e5-8d4c-0bbfcc3aead3.png</url>
      <title>DEV Community: hashem alharhashi</title>
      <link>https://dev.to/hashem_alharhashi_3777a84</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hashem_alharhashi_3777a84"/>
    <language>en</language>
    <item>
      <title>I shipped 65 tools. Then I deleted 64 of them.</title>
      <dc:creator>hashem alharhashi</dc:creator>
      <pubDate>Wed, 12 Aug 2026 12:44:56 +0000</pubDate>
      <link>https://dev.to/hashem_alharhashi_3777a84/i-shipped-65-tools-then-i-deleted-64-of-them-506o</link>
      <guid>https://dev.to/hashem_alharhashi_3777a84/i-shipped-65-tools-then-i-deleted-64-of-them-506o</guid>
      <description>&lt;p&gt;A year ago I shipped a site with 65 small image tools. Resize, crop, compress, convert, rotate — the usual grid of utilities. It looked productive. It wasn't.&lt;/p&gt;

&lt;p&gt;Analytics said something uncomfortable: almost nobody used 64 of them. One tool had returning users, real session time, and people coming back with different files. It was the image-to-SVG vectorizer.&lt;/p&gt;

&lt;p&gt;So I deleted the other 64.&lt;/p&gt;

&lt;p&gt;Here's what I learned rebuilding around the one thing that worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grouping traced paths by exact hex is useless on real images
&lt;/h2&gt;

&lt;p&gt;The vectorizer has a colour/layer editor — pick a colour, hide it or recolour it. The obvious implementation groups SVG paths by their exact fill value.&lt;/p&gt;

&lt;p&gt;That works beautifully on the synthetic test logo I built it against. Then a user sent me a real file: a gold emblem, photographed.&lt;/p&gt;

&lt;p&gt;The trace produced &lt;strong&gt;3,428 paths across 3,122 distinct hex values.&lt;/strong&gt; Almost every path had its own colour. The palette showed the top 24 by frequency, which together covered &lt;strong&gt;83 paths — 2% of the image.&lt;/strong&gt; Clicking a swatch did nothing you could see. The feature was technically working and practically broken.&lt;/p&gt;

&lt;p&gt;The fix was to stop treating colours as identities and start treating them as positions in a space. Seed from the heaviest colours, reject seeds that are too close to an existing one, then assign every colour to its nearest seed using a weighted-RGB distance:&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;dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;rm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;dr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&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;dg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&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="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dg&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;db&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;That weighting is a cheap approximation of human colour perception — green gets the heaviest coefficient because we discriminate green far better than blue.&lt;/p&gt;

&lt;p&gt;Result: 3,122 colours collapsed to &lt;strong&gt;18 layers covering 100% of paths.&lt;/strong&gt; Hiding the top layer now removes 610 paths and you watch the background disappear.&lt;/p&gt;

&lt;p&gt;One detail that mattered more than expected: when recolouring, don't flatten the layer to a single value. Store each path's original colour, compute its offset from the cluster centre, and re-apply that offset to the new colour. Otherwise every gradient and shadow in that layer turns into a flat blob.&lt;/p&gt;

&lt;h2&gt;
  
  
  I shipped a 26MB WASM model and had to rip it out
&lt;/h2&gt;

&lt;p&gt;Small inputs trace badly, so I added an ONNX upscaling model to pre-enlarge them. Locally it was great.&lt;/p&gt;

&lt;p&gt;In production, the WASM binary ran 13–26MB depending on the build, and on some machines its &lt;strong&gt;compilation&lt;/strong&gt; — not inference, compilation — locked the main thread for minutes. On my machine, and in a clean browser profile, it was fine. That's the worst kind of bug: it only exists for other people.&lt;/p&gt;

&lt;p&gt;I removed it entirely. The site went from 18MB to 1.4MB, and the quality difference on real inputs was smaller than I'd told myself it was.&lt;/p&gt;

&lt;p&gt;If you're shipping a model to the browser, budget for compile time on cold, weak, extension-loaded machines — not just inference time on yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture that made deleting 64 tools worth it
&lt;/h2&gt;

&lt;p&gt;Everything runs client-side. VTracer (Rust) compiled to WebAssembly, executed in a Web Worker with OffscreenCanvas so the UI stays responsive during a multi-second trace.&lt;/p&gt;

&lt;p&gt;That's not a purity thing, it's what makes the product viable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No upload&lt;/strong&gt; — files never leave the machine, which matters when the file is a client's unreleased logo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No rate limit&lt;/strong&gt; — there's no server cost to ration, so there's no reason to cap anyone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline&lt;/strong&gt; after the page loads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account&lt;/strong&gt;, because there's nothing to store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One parameter tuning note, since it cost me real time to measure: &lt;code&gt;colorPrecision&lt;/code&gt; at 8 produced 3,590 paths / 1,577KB. At 6 it produced 2,856 paths / 1,139KB — &lt;strong&gt;28% smaller files and 32% faster&lt;/strong&gt;, with output I genuinely could not tell apart on logos. Default it lower than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual lesson
&lt;/h2&gt;

&lt;p&gt;Shipping 65 things felt like 65 chances to win. It was really one diluted product with 65 half-maintained surfaces, no clear reason to return, and nothing anyone would describe to a friend.&lt;/p&gt;

&lt;p&gt;The surviving tool is here if you want to break it: &lt;strong&gt;&lt;a href="https://12pixa.com" rel="noopener noreferrer"&gt;https://12pixa.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thin script fonts and scanned line drawings are where tracers fall apart — if you find a file it handles badly, I'd genuinely rather hear that than a compliment.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
