<?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: Khatai Huseynzada</title>
    <description>The latest articles on DEV Community by Khatai Huseynzada (@bilgegates).</description>
    <link>https://dev.to/bilgegates</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%2F3883065%2F9ae2042e-49f4-4d4d-b991-a01ab21ced16.jpg</url>
      <title>DEV Community: Khatai Huseynzada</title>
      <link>https://dev.to/bilgegates</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bilgegates"/>
    <language>en</language>
    <item>
      <title>7 months building an open-source chess tool. Here's what actually happened.</title>
      <dc:creator>Khatai Huseynzada</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:53:19 +0000</pubDate>
      <link>https://dev.to/bilgegates/7-months-building-an-open-source-chess-tool-heres-what-actually-happened-1g60</link>
      <guid>https://dev.to/bilgegates/7-months-building-an-open-source-chess-tool-heres-what-actually-happened-1g60</guid>
      <description>&lt;p&gt;I started this project in December 2025. Not because anyone asked. Not because I saw a gap in the market. Because I compose chess problems as a hobby and every tool I tried to export a diagram with either watermarked the output, required an account, or exported at 72 DPI like it was still 2003.&lt;/p&gt;

&lt;p&gt;So I built my own. Seven months later: 5 stars on GitHub, 6 forks, a handful of npm downloads, about $13 spent on hosting.&lt;/p&gt;

&lt;p&gt;That's the whole story, numbers-wise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I kept going
&lt;/h2&gt;

&lt;p&gt;The tool actually works. That sounds like a low bar, but a lot of side projects don't clear it.&lt;/p&gt;

&lt;p&gt;You paste in a FEN string, configure the board however you want — colors, piece set, whether to show coordinates — and export a PNG at up to 1200 DPI with real physical dimensions. You tell it "6cm board at 600 DPI" and it gives you a file you can drop straight into a print layout. No watermark. No account. No ads.&lt;/p&gt;

&lt;p&gt;I added cloud sync, batch export (up to 10 positions as a ZIP), a FEN history, a position database search. Probably spent too long on some of that. But the core thing works exactly as I wanted it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs that actually taught me something
&lt;/h2&gt;

&lt;p&gt;Safari has a GPU memory limit on canvas elements that Chrome doesn't. If you don't reset &lt;code&gt;canvas.width = 0; canvas.height = 0&lt;/code&gt; after every blob export, iOS will crash — silently, with no useful error. I hit this, spent a while confused about why it only happened on iPhones, then traced it down and fixed it. Now it's a documented invariant in the codebase: you touch the canvas export path, you reset the dimensions after. No exceptions.&lt;/p&gt;

&lt;p&gt;The FEN parser has co-located unit tests and a rule: every change requires a new test case. It sounds strict but it's saved me twice.&lt;/p&gt;

&lt;p&gt;These are the parts I'm actually proud of.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;I thought if the tool was good, people would find it. That's not how it works.&lt;/p&gt;

&lt;p&gt;GitHub has no discovery mechanism. npm sorts by download count — which means new packages are invisible by default. The people who need your tool don't know it exists and aren't searching for it by name.&lt;/p&gt;

&lt;p&gt;I also built for a narrow audience without fully accepting what that meant. Chess composers are a small group. Most chess players don't need to export diagrams to print. I knew this going in, but I still somehow expected more traction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The npm package
&lt;/h2&gt;

&lt;p&gt;A month ago I extracted the rendering core into a separate package: &lt;code&gt;@chessvision-org/chess-vision&lt;/code&gt;. Zero dependencies, works in Node.js, Deno, Bun, and the browser. Pure SVG output — no DOM, no canvas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;generateDiagram&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;@chessvision-org/chess-vision&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;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 idea was that even if the web app never takes off, the package might be useful to someone building a chess blog or generating diagrams server-side. It's only been on npm a week so I have no idea yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm at
&lt;/h2&gt;

&lt;p&gt;The code is the best it's ever been. The tool does what I wanted it to do. I've learned more about Canvas, Safari memory behavior, Supabase RLS, and FEN parsing edge cases than I ever would have otherwise.&lt;/p&gt;

&lt;p&gt;But I haven't figured out how to reach the people who would actually use it. That part is harder than the technical stuff. I'm more comfortable writing a parser than writing a cold message to a chess magazine editor.&lt;/p&gt;

&lt;p&gt;I'm going to keep working on it. Just with less time on features and more time actually talking to people.&lt;/p&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/chessvision-org/chess-vision" rel="noopener noreferrer"&gt;github.com/chessvision-org/chess-vision&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Tool: &lt;a href="https://chessvision.org" rel="noopener noreferrer"&gt;chessvision.org&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Package: &lt;a href="https://www.npmjs.com/package/@chessvision-org/chess-vision" rel="noopener noreferrer"&gt;@chessvision-org/chess-vision&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've been through something similar — built something real, got nowhere with distribution — I'd like to hear what you did.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>career</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to render chess diagrams in Node.js (no DOM, no canvas, no headless browser)</title>
      <dc:creator>Khatai Huseynzada</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:05:02 +0000</pubDate>
      <link>https://dev.to/bilgegates/how-to-render-chess-diagrams-in-nodejs-no-dom-no-canvas-no-headless-browser-44d</link>
      <guid>https://dev.to/bilgegates/how-to-render-chess-diagrams-in-nodejs-no-dom-no-canvas-no-headless-browser-44d</guid>
      <description>&lt;p&gt;If you've ever needed to generate a chess diagram programmatically — for a blog, a PDF report, or a static site — you've probably hit the same wall I did.&lt;/p&gt;

&lt;p&gt;Most solutions require either a browser environment, a headless Chromium instance, or a canvas polyfill that pulls in half of npm. For something as simple as "turn a FEN string into an image," that's a lot of overhead.&lt;/p&gt;

&lt;p&gt;So I extracted the rendering core from my chess editor into a standalone package: &lt;code&gt;@chessvision-org/chess-vision&lt;/code&gt;. Zero dependencies. Works in Node.js, Deno, Bun, and the browser. No DOM, no canvas, no network calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @chessvision-org/chess-vision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;generateDiagram&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;@chessvision-org/chess-vision&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;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;&lt;code&gt;svg&lt;/code&gt; is a self-contained SVG string — inline it in HTML, write it to a file, embed it in a PDF. Pieces are inlined as SVG paths (CBurnett / Lichess style), so no external resources are needed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generateDiagram&lt;/code&gt; throws if the FEN string is invalid. Validate first if you're working with untrusted input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateFEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getFENValidationError&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;@chessvision-org/chess-vision&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateFEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&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="nf"&gt;getFENValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// e.g. 'Board must have 8 ranks'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;
  
  
  Real use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Static site or blog
&lt;/h3&gt;

&lt;p&gt;If you write about chess and want diagrams in your posts, generate them at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;writeFileSync&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;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateDiagram&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;@chessvision-org/chess-vision&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;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italian.svg&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkb1r/pp3ppp/3ppn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sicilian.svg&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="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fen&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="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./diagrams/&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 output SVGs embed directly in Markdown, MDX, or HTML with no image hosting needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side rendering
&lt;/h3&gt;

&lt;p&gt;Generate diagrams on request without spinning up a browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateFEN&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;@chessvision-org/chess-vision&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&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;/diagram&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fen&lt;/span&gt; &lt;span class="o"&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fen&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;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;validateFEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fen&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid FEN&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;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;image/svg+xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svg&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;h3&gt;
  
  
  Astro / Next.js at build time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In an Astro component or Next.js getStaticProps&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateDiagram&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;@chessvision-org/chess-vision&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;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;flipped&lt;/span&gt;&lt;span class="p"&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;span class="c1"&gt;// Render directly — no img tag, no src, no upload&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;Fragment set:html={svg} /&amp;gt;   ← Astro&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;div dangerouslySetInnerHTML={{ __html: svg }} /&amp;gt;  ← React (SVG is from your own code, not user input)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Themes and customization
&lt;/h2&gt;

&lt;p&gt;The package ships 20 built-in board themes (&lt;code&gt;classic&lt;/code&gt;, &lt;code&gt;ocean&lt;/code&gt;, &lt;code&gt;wood&lt;/code&gt;, &lt;code&gt;forest&lt;/code&gt;, &lt;code&gt;navy&lt;/code&gt;, &lt;code&gt;marble&lt;/code&gt;, and more):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getBoardTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listThemeIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;themeCoordinateColor&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;@chessvision-org/chess-vision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;listThemeIds&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// → ['classic', 'brown', 'wood', 'sand', 'slate', 'marble', 'blue', 'ocean',&lt;/span&gt;
&lt;span class="c1"&gt;//    'green', 'forest', 'mint', 'purple', 'lavender', 'red', 'coral',&lt;/span&gt;
&lt;span class="c1"&gt;//    'sunset', 'pink', 'burgundy', 'navy', 'ice']&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getBoardTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ocean&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="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// → { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lightSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;darkSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;showCoords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;coordColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;themeCoordinateColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// → 'white' or 'black', whichever is more legible&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or pass any hex colors directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateDiagram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lightSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e8f4f8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;darkSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#7eb8da&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;flipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Black's perspective&lt;/span&gt;
  &lt;span class="na"&gt;showFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// thin outer frame&lt;/span&gt;
  &lt;span class="na"&gt;coordColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// coordinate label color&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Starting position&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// aria-label on the SVG&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FEN utilities
&lt;/h2&gt;

&lt;p&gt;The package also ships a full FEN parser if you need to work with positions directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;parseFEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;boardToFEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;movePiece&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getPieceAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;countPieces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;materialBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;findKing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;STARTING_FEN&lt;/span&gt;&lt;span class="p"&gt;,&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;@chessvision-org/chess-vision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Parse to an 8×8 matrix (row 0 = rank 8, row 7 = rank 1)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STARTING_FEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// → 'K'  (white king on e1)&lt;/span&gt;

&lt;span class="nf"&gt;getPieceAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → 'K'&lt;/span&gt;
&lt;span class="nf"&gt;getPieceAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → 'q'&lt;/span&gt;

&lt;span class="c1"&gt;// Move a piece — pure, returns a new board, never mutates&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;movePiece&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e2&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;e4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;boardToFEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → updated piece placement string&lt;/span&gt;

&lt;span class="nf"&gt;countPieces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// → { P: 8, N: 2, B: 2, R: 2, Q: 1, K: 1, p: 8, … }&lt;/span&gt;
&lt;span class="nf"&gt;materialBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → 0  (equal material)&lt;/span&gt;
&lt;span class="nf"&gt;findKing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// → 'e1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full FEN record
&lt;/h2&gt;

&lt;p&gt;Parse and serialize all six FEN fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;parseFENRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buildFENRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleActiveColor&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;@chessvision-org/chess-vision&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;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFENRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → { board, activeColor: 'b', castling: 'KQkq', enPassant: 'e3', halfmove: 5, fullmove: 12 }&lt;/span&gt;

&lt;span class="nf"&gt;buildFENRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → 'rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12'&lt;/span&gt;

&lt;span class="nf"&gt;toggleActiveColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;activeColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// → 'w'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why SVG
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Scales to any resolution without quality loss&lt;/li&gt;
&lt;li&gt;Self-contained — 23 piece sets inlined as paths, no external resources&lt;/li&gt;
&lt;li&gt;Embeds directly in HTML and most PDF libraries&lt;/li&gt;
&lt;li&gt;Converts to raster easily with &lt;code&gt;sharp&lt;/code&gt; if you need PNG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need high-res PNG for print, the web app (&lt;a href="https://chessvision.org" rel="noopener noreferrer"&gt;chessvision.org&lt;/a&gt;) handles that — up to 1200 DPI with accurate physical dimensions in centimeters.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@chessvision-org/chess-vision" rel="noopener noreferrer"&gt;@chessvision-org/chess-vision&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/chessvision-org/chess-vision-utils" rel="noopener noreferrer"&gt;chessvision-org/chess-vision-utils&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: AGPL-3.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open to feedback on the API — if something feels awkward to use or there's a use case I haven't covered, I'd like to know.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>node</category>
      <category>typescript</category>
      <category>chess</category>
    </item>
    <item>
      <title>I built an open-source chess diagram editor because every existing tool was either ugly or paywalled</title>
      <dc:creator>Khatai Huseynzada</dc:creator>
      <pubDate>Tue, 23 Jun 2026 17:44:55 +0000</pubDate>
      <link>https://dev.to/bilgegates/i-built-an-open-source-chess-diagram-editor-because-every-existing-tool-was-either-ugly-or-paywalled-481f</link>
      <guid>https://dev.to/bilgegates/i-built-an-open-source-chess-diagram-editor-because-every-existing-tool-was-either-ugly-or-paywalled-481f</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhxqw7i8xhedsqzwzm8v3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhxqw7i8xhedsqzwzm8v3.png" alt=" " width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've ever tried to include a chess diagram in a blog post, a study document, or a video thumbnail, you know the pain. Most tools are either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A screenshot from a UI that wasn't designed for clean export&lt;/li&gt;
&lt;li&gt;Decent but behind a paywall or watermark&lt;/li&gt;
&lt;li&gt;Good but abandoned, last updated in 2015&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got tired of it and built &lt;strong&gt;&lt;a href="https://chessvision.org" rel="noopener noreferrer"&gt;ChessVision&lt;/a&gt;&lt;/strong&gt; — a completely free, open-source chess diagram editor that runs entirely in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The core loop is simple: paste a FEN, set up the position you want, export the image. That's it.&lt;/p&gt;

&lt;p&gt;But the details matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export quality that actually holds up for print&lt;/strong&gt;&lt;br&gt;
You can export at 1×, 2×, 3×, or 4× resolution — the top setting gives you 1200 DPI. Board size is set in centimetres, so when you say "8cm board," it prints at 8cm. DPI metadata is embedded in the PNG/JPEG so the file knows its own physical size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch export&lt;/strong&gt;&lt;br&gt;
Up to 10 positions simultaneously, downloaded as a ZIP. Useful for problem sets, opening repertoire PDFs, anything where you need a series of diagrams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization that isn't an afterthought&lt;/strong&gt;&lt;br&gt;
23 piece sets sourced from the Lichess CDN, 20 preset board themes, a full HSV/RGB/HEX color picker for custom colors, and the ability to save up to 48 theme presets. The thin board frame, coordinate labels, and flip orientation are all toggleable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position database search&lt;/strong&gt;&lt;br&gt;
From inside the editor, you can search Lichess, PDB (the endgame problem database), and YACPDB for the current position. It shows up directly in the sidebar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional cloud sync&lt;/strong&gt;&lt;br&gt;
There's an account system (email + password + optional TOTP 2FA) backed by Supabase. Your FEN history, themes, and settings sync across devices. But you don't need an account for anything — all core features work locally, no sign-in required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React 19&lt;/strong&gt; with TypeScript strict mode throughout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite 8&lt;/strong&gt; for bundling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@dnd-kit&lt;/strong&gt; for the drag-and-drop board editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; with CSS custom properties for theming&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas API&lt;/strong&gt; for rendering — no third-party chart or image library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for auth and cloud sync (row-level security, TOTP MFA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Workers&lt;/strong&gt; for heavy raster export so the UI stays responsive during batch jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What was actually hard
&lt;/h2&gt;

&lt;p&gt;The canvas rendering pipeline took the most iteration. The challenge isn't drawing squares and pieces — that's straightforward. It's making the output &lt;em&gt;physically accurate&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG and JPEG have DPI metadata fields (&lt;code&gt;pHYs&lt;/code&gt; chunk for PNG, EXIF for JPEG). Browsers don't write these automatically. I had to implement the chunk insertion manually to make "export at 8cm, 600 DPI" actually mean something when you open the file in Illustrator or send it to a printer.&lt;/li&gt;
&lt;li&gt;SVG export required inlining the piece images as base64 so the file is self-contained. Hotlinking a CDN from an SVG that might be opened offline or embedded in a PDF doesn't work.&lt;/li&gt;
&lt;li&gt;The batch exporter runs in a Web Worker with a pause/resume/cancel interface, because blocking the main thread for 10 × 1200 DPI canvas renders is obviously not acceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It's fully open source
&lt;/h2&gt;

&lt;p&gt;MIT would've been my preference but the piece sets come from Lichess which is AGPL, so the project is AGPL-3.0.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/chessvision-org/chess-vision" rel="noopener noreferrer"&gt;https://github.com/chessvision-org/chess-vision&lt;/a&gt;&lt;br&gt;
→ &lt;strong&gt;Live&lt;/strong&gt;: &lt;a href="https://chessvision.org" rel="noopener noreferrer"&gt;https://chessvision.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PRs welcome. The contributing guide is intentionally short — &lt;code&gt;pnpm validate&lt;/code&gt; passing is the main gate.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by Khatai Huseynzada. If you use it for something, I'd love to hear about it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chess</category>
      <category>opensource</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>ChessVision – client-side chess diagram generator</title>
      <dc:creator>Khatai Huseynzada</dc:creator>
      <pubDate>Tue, 26 May 2026 15:08:06 +0000</pubDate>
      <link>https://dev.to/bilgegates/chessvision-client-side-chess-diagram-generator-2e7i</link>
      <guid>https://dev.to/bilgegates/chessvision-client-side-chess-diagram-generator-2e7i</guid>
      <description>&lt;p&gt;Show HN: ChessVision – client-side chess diagram generator with print-quality export&lt;/p&gt;

&lt;p&gt;I built ChessVision, a browser-based tool that renders FEN positions to high-resolution raster and vector images. Everything runs client-side — no backend, no data leaves the device.&lt;/p&gt;

&lt;p&gt;The main use case is chess players, coaches, and authors who need precise board diagrams for books, articles, or presentations without depending on a server.&lt;/p&gt;

&lt;p&gt;A few things that might be interesting to HN:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG/JPEG export up to 30,208 × 30,208 px, processed entirely in the browser via Canvas API&lt;/li&gt;
&lt;li&gt;SVG export and ZIP-bundled batch export across multiple positions and formats&lt;/li&gt;
&lt;li&gt;23 piece sets, 20+ themes, full custom color picker (HSL/RGB/HEX)&lt;/li&gt;
&lt;li&gt;FEN history with favorites, pinning, archive, and search&lt;/li&gt;
&lt;li&gt;Safari canvas-disposal invariant enforced to prevent memory leaks on export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack: React 19, TypeScript 6, Vite 8, Tailwind CSS 4. Semantic versioning with automated releases via conventional commits.&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://chess-vision-site.vercel.app" rel="noopener noreferrer"&gt;https://chess-vision-site.vercel.app&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/BilgeGates/chess-vision" rel="noopener noreferrer"&gt;https://github.com/BilgeGates/chess-vision&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the export pipeline or the client-side rendering approach.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Using console.log(): 5 Hidden Chrome DevTools Superpowers 🚀</title>
      <dc:creator>Khatai Huseynzada</dc:creator>
      <pubDate>Thu, 16 Apr 2026 19:25:27 +0000</pubDate>
      <link>https://dev.to/bilgegates/stop-using-consolelog-5-hidden-chrome-devtools-superpowers-628</link>
      <guid>https://dev.to/bilgegates/stop-using-consolelog-5-hidden-chrome-devtools-superpowers-628</guid>
      <description>&lt;p&gt;Let's be honest: as web developers, whether we are working on the front-end or back-end, we spend half of our lives staring at the browser's Developer Tools. However, the vast majority of us only scratch the surface of what this massive Swiss Army knife can actually do.&lt;/p&gt;

&lt;p&gt;If your primary debugging strategy is still littering your codebase with &lt;code&gt;console.log("here 1")&lt;/code&gt; and &lt;code&gt;console.log("data", data)&lt;/code&gt;, you are losing valuable time. &lt;/p&gt;

&lt;p&gt;Let's dive into 5 lesser-known Chrome DevTools features that will instantly speed up your workflow and make your debugging process a lot less painful.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Convert Network Requests to Code Instantly ("Copy as Fetch") 🌐
&lt;/h2&gt;

&lt;p&gt;Imagine a complex API request is firing on your website, and you want to test that exact same request in your terminal, a Node.js script, or Postman. Manually typing out all the headers, authorization tokens, and stringified payloads is a nightmare.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;Network&lt;/strong&gt; tab in DevTools.&lt;/li&gt;
&lt;li&gt;Right-click the request you want to duplicate.&lt;/li&gt;
&lt;li&gt;Hover over &lt;strong&gt;Copy&lt;/strong&gt; and select &lt;strong&gt;Copy as fetch&lt;/strong&gt; (or &lt;code&gt;Copy as cURL&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Boom! The browser instantly generates the exact code needed to replicate that request. You can paste it directly into your code editor or console and tweak it as needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Visualize Arrays and Objects with &lt;code&gt;console.table()&lt;/code&gt; 📊
&lt;/h2&gt;

&lt;p&gt;When fetching an array of objects from an API, a standard &lt;code&gt;console.log()&lt;/code&gt; results in a deeply nested, messy output in the console that requires endless clicking to expand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Hack:&lt;/strong&gt;&lt;br&gt;
Swap out your &lt;code&gt;console.log(data)&lt;/code&gt; for &lt;code&gt;console.table(data)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The browser will automatically parse your array of objects and render it as a beautifully formatted, highly readable table. Even better? You can click the column headers in the console to sort the data!&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Turn Your Browser into a Live Text Editor (&lt;code&gt;designMode&lt;/code&gt;) ✍️
&lt;/h2&gt;

&lt;p&gt;A designer or client asks you to "quickly tweak the wording" on a landing page to see how it looks. Switching back to your IDE, finding the exact text node, changing it, and waiting for the page to reload is incredibly tedious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Hack:&lt;/strong&gt;&lt;br&gt;
Open your Console and type this magic spell:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;document.designMode = "on";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hit Enter. Now, you can click on &lt;em&gt;any&lt;/em&gt; text element on the webpage and edit it live, exactly like a Microsoft Word document. It’s an absolute lifesaver for rapid UI testing and typography adjustments.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Lightning-Fast DOM Access in the Console (&lt;code&gt;$0&lt;/code&gt;) ⚡
&lt;/h2&gt;

&lt;p&gt;You are inspecting an HTML element in the &lt;strong&gt;Elements&lt;/strong&gt; tab and want to manipulate it using JavaScript in the console. Most developers waste time writing &lt;code&gt;document.querySelector('.my-long-class-name')&lt;/code&gt; to grab it.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Simply click on any element in the &lt;strong&gt;Elements&lt;/strong&gt; tab so it's highlighted.&lt;/li&gt;
&lt;li&gt;Switch over to the &lt;strong&gt;Console&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;$0&lt;/code&gt; and hit Enter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! &lt;code&gt;$0&lt;/code&gt; is a magic variable that holds the reference to the most recently inspected element. You can immediately start calling methods on it, like &lt;code&gt;$0.classList.add('hidden')&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Bonus: DevTools remembers your history! You can use &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, etc., to reference previously inspected elements).&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Debug Loops Painlessly with Conditional Breakpoints 🛑
&lt;/h2&gt;

&lt;p&gt;You have a &lt;code&gt;for&lt;/code&gt; loop that iterates 1,000 times, but a bug only occurs when &lt;code&gt;i === 499&lt;/code&gt;. If you drop a standard breakpoint inside the loop, execution will pause on the very first iteration, and you'll have to click "Resume" 499 times. No thanks.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;Sources&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Right-click on the line number where you want to pause.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Add conditional breakpoint...&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Type your condition (e.g., &lt;code&gt;i === 499&lt;/code&gt; or &lt;code&gt;user.role === 'admin'&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Execution will completely ignore the breakpoint &lt;em&gt;unless&lt;/em&gt; your exact condition evaluates to true. This reduces your bug-hunting time from hours to seconds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;Chrome DevTools is much more than just a place to read error messages. By incorporating these small hacks into your daily routine, you'll drastically reduce friction in your development process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which DevTools feature can you not live without? Did I miss your favorite? Let me know in the comments below! 👇&lt;/strong&gt;&lt;/p&gt;

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