<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ElCritch's Musings]]></title><description><![CDATA[ElCritch's Musings]]></description><link>https://blog.elcritch.net</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1736835206875/c51a58c4-e054-47ce-a5fe-ef38869d1bf6.png</url><title>ElCritch&apos;s Musings</title><link>https://blog.elcritch.net</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 18:24:19 GMT</lastBuildDate><atom:link href="https://blog.elcritch.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Debugging Atlas Installs]]></title><description><![CDATA[Atlas has become my goto for handling Nim dependencies. Though I’m biased as I helped get it up to speed. A large part of that work was refactoring how Atlas loads dependencies and their version information. It now loads every version of every possib...]]></description><link>https://blog.elcritch.net/debugging-atlas-installs</link><guid isPermaLink="true">https://blog.elcritch.net/debugging-atlas-installs</guid><category><![CDATA[Nim]]></category><category><![CDATA[nimble]]></category><category><![CDATA[atlas]]></category><category><![CDATA[dependency management]]></category><category><![CDATA[SAT]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Wed, 01 Oct 2025 00:16:59 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/nim-lang/atlas">Atlas</a> has become my goto for handling Nim dependencies. Though I’m biased as I helped get it up to speed. A large part of that work was refactoring how Atlas loads dependencies and their version information. It now loads every version of every possible dependencies enabling the SAT solver to solve for appropriate versions.</p>
<p>This approach spits out a lot of information however. Most of it is hidden, but even with default verbosity there’s a substantial amount of output. This isn’t quite as annoying as verbosity with Nimble since Atlas only needs to be run to add new dependencies or to do updates (often weeks pass between needing to run Atlas). Otherwise with Atlas you just run <a target="_blank" href="https://github.com/nim-lang/nim">Nim</a>.</p>
<p><strong><em>Note</em></strong>: <em>My personal preference has become to add a</em> <code>build</code> <em>or</em> <code>test</code> <em>task to my top level</em> <a target="_blank" href="https://github.com/nim-lang/atlas/blob/master/config.nims"><code>config.nims</code></a> <em>file. Then just run</em> <code>nim build</code> <em>or</em> <code>nim test</code> <em>and skipping tasks in the Nimble file.</em></p>
<p>Back to Atlas. Someone posted an issue getting a recent version of HappyX to install. That was an easily <a target="_blank" href="https://github.com/nim-lang/atlas/pull/164">fixed</a> bug due to <code>#head</code> being treated specially.</p>
<p>After fixing the bug Atlas <em>still</em> wasn’t selecting a recent version of HappyX and going back to version <code>2.6.1</code>. This time however it’s due to issues in the dependencies. I figured it’d make a good tutorial writeup.</p>
<p>Lets take a look at the output on default verbosity:</p>
<pre><code class="lang-bash">$ atlas use happyx
... <span class="hljs-comment"># logs full of downloading git repos; lets skip it</span>
$ atlas install
[Notice] (atlas:project) Using project directory: /Volumes/projects/nims/test-proj
[Notice] (test_proj) expanding root package at: /Volumes/projects/nims/test-proj url: atlas:///Volumes/projects/nims/test-proj/test_proj.nimble
[Notice] (atlas:expand) Expanding packages <span class="hljs-keyword">for</span>: test_proj
[Notice] (test_proj) Initializing packages: test_proj
[Notice] (test_proj) Processing packages: test_proj
[Warn]   (gitops) Running Git failed: 1 <span class="hljs-built_in">command</span>: `git -C /Volumes/projects/nims/test-proj <span class="hljs-built_in">log</span> -n1 --format=%H `
[Warn]   (gitops) Running Git failed: 1 <span class="hljs-built_in">command</span>: `git -C /Volumes/projects/nims/test-proj <span class="hljs-built_in">log</span> --format=%H origin/HEAD -n1`
[Warn]   (test-proj) could not find origin head at: /Volumes/projects/nims/test-proj
[Warn]   (test_proj) nimble file missing version information: <span class="hljs-comment">#head@-</span>
[Notice] (test_proj) Initializing packages: happyx
[Notice] (test_proj) Processing packages: happyx
[Notice] (test_proj) Initializing packages: checksums, cligen, illwill, regex, httpx, microasynchttpserver, httpbeast, nimja, websocket, websocketx, nimcrypto, oauth, nimpy, ws
[Notice] (test_proj) Processing packages: checksums, cligen, illwill, regex, httpx, microasynchttpserver, httpbeast, nimja, websocket, websocketx, nimcrypto, oauth, nimpy, ws
[Notice] (test_proj) Initializing packages: unicodedb, unicodeplus, ioselectors, picohttpparser, asynctools, asynctools.iffy.github.com, asynctools.timotheecour.github.com, sha1
[Notice] (test_proj) Processing packages: unicodedb, unicodeplus, ioselectors, picohttpparser, asynctools, asynctools.iffy.github.com, asynctools.timotheecour.github.com, sha1
[Notice] (test_proj) Initializing packages: segmentation, graphemes, wepoll, timerwheel
[Notice] (test_proj) Processing packages: segmentation, graphemes, wepoll, timerwheel
[Warn]   (gitops) Running Git failed: 128 <span class="hljs-built_in">command</span>: `git -C /Volumes/projects/nims/test-proj/deps/asynctools.iffy.github.com rev-parse pr_fix_for_latest`
[Warn]   (gitops) Running Git failed: 128 <span class="hljs-built_in">command</span>: `git -C /Volumes/projects/nims/test-proj/deps/asynctools.timotheecour.github.com rev-parse pr_fix_compilation`
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.9.1@87b5c209 were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.9.2@7089cf54 were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.9.7@bed3612d were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.9.8@e7000a37 were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.10.1@7c3c88d1 were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.11.6@9722ab55 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.0.0@6f8a3082 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.1.0@31ae74c7 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.3.0@d1180fc5 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.5.2@e5f0dcd8 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.8.0@52105ea1 were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.8.1@9a23df3a were not able to be satisfied: cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.9.0@3a6dc5d6 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.10.2@7f77fb84 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 3.11.0@79680368 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.0.0@acfdb62d were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.0.2@edd17842 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.2.1@f4ee8d19 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.3.0@451cc123 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.3.1@b225bae5 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.4.0@f41fa4ac were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.4.2@8895b6b0 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.5.0@5c0d3cb7 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.5.2@cfd717ec were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.6.0@200a174c were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.6.1@689e3e4a were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.6.2@06d12574 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.6.5@5476224f were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.7.0@8c0be200 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.7.1@730fcf61 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.7.2@6ac49882 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.7.3@06437f69 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 4.7.4@6298ad77 were not able to be satisfied: checksums &gt;= 0.1.0; cligen &gt;= 1.6.14; illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; regex #head; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9</span>
[Notice] (atlas:resolved) inactive packages: checksums, oauth, unicodeplus, asynctools.iffy.github.com, asynctools.timotheecour.github.com, sha1, segmentation, graphemes, timerwheel
[Notice] (atlas:resolved) selected:
[Notice] (atlas:resolved) [ ] (happyx, 2.5.1@d580ce10)
[Notice] (atlas:resolved) [x] (happyx, 2.6.1@9ce225f3)
[Notice] (atlas:resolved) [ ] (happyx, 2.9.1@87b5c209)
[Notice] (atlas:resolved) [ ] (happyx, 0.3.0@e0ee858b)
[Notice] (atlas:resolved) [ ] (happyx, 4.7.4@6298ad77)
[Notice] (atlas:resolved) [ ] (cligen, 1.9.1@79f1566e)
[Notice] (atlas:resolved) [x] (cligen, 1.9.2@c41e6e8a)
[Notice] (atlas:resolved) [ ] (cligen, 0.9.4@231bdc9e)
[Notice] (atlas:resolved) [ ] (illwill, 0.4.0@631169fe)
[Notice] (atlas:resolved) [x] (illwill, 0.4.1@99a120f7^)
[Notice] (atlas:resolved) [ ] (illwill, 0.2.0@fe6f670c)
[Notice] (atlas:resolved) [ ] (regex, 0.26.3@4593305e)
[Notice] (atlas:resolved) [x] (regex, 0.26.3@88f634b9^)
[Notice] (atlas:resolved) [ ] (regex, <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d@199e696a)</span>
[Notice] (atlas:resolved) [ ] (httpx, 0.3.7@9fab2c3c)
[Notice] (atlas:resolved) [x] (httpx, 0.3.8@47cb5e79^)
[Notice] (atlas:resolved) [ ] (httpx, 0.1.0@4b904711)
[Notice] (atlas:resolved) [ ] (microasynchttpserver, 0.10.2@90118afb)
[Notice] (atlas:resolved) [x] (microasynchttpserver, 0.11.0@2f206514^)
[Notice] (atlas:resolved) [ ] (microasynchttpserver, 0.9.0@77a5bada)
[Notice] (atlas:resolved) [ ] (httpbeast, 0.4.0@8b50e378)
[Notice] (atlas:resolved) [x] (httpbeast, 0.4.1@abc13d11)
[Notice] (atlas:resolved) [ ] (httpbeast, 0.1.0@ff8fe095)
...
</code></pre>
<p>Okay so looking at the lines with <code>[Notice] (atlas:resolved)</code> we see which dependency versions the SAT algorithm has selected. As we can see it selected <code>[Notice] (atlas:resolved) [x] (happyx, 2.6.1@9ce225f3)</code>.</p>
<p>We want to figure understand <em>why</em> it selected that version so let’s dig into the output to see which versions were <em>available</em> for the SAT solver. That’s usually the key to understanding and resolving any issues. To help with this Atlas outputs warnings when versions of a given dependency are rejected. In this case we see a lot of lines which look like:</p>
<pre><code class="lang-bash">[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 2.9.1@87b5c209 were not able to be satisfied: cligen &gt;= 1.6.14; illwill &gt;= 0.3.1; regex <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d; httpx &gt;= 0.3.7; microasynchttpserver &gt;= 0.11.0; httpbeast &gt;= 0.4.2; nimja &gt;= 0.8.7; websocket &gt;= 0.5.2; websocketx &gt;= 0.1.2; nimcrypto &gt;= 0.3.9; oauth &gt;= 0.10; nimpy &gt;= 0.2.0</span>
...
</code></pre>
<p>Let’s break up the line for HappyX version <code>4.7.4</code> (the active dependency Atlas is processing is listed in the parens):</p>
<pre><code class="lang-bash">[Warn]   (happyx) no versions matched requirements <span class="hljs-keyword">for</span> the dependency: httpbeast
[Warn]   (happyx) all requirements needed <span class="hljs-keyword">for</span> nimble release: 
    4.7.4@6298ad77 were not able to be satisfied: 
           checksums &gt;= 0.1.0; 
           cligen &gt;= 1.6.14;
           illwill <span class="hljs-comment">#2fe96f5c5a6e216e84554d92090ce3d47460667a; </span>
           regex <span class="hljs-comment">#head; </span>
           httpx &gt;= 0.3.7; 
           microasynchttpserver &gt;= 0.11.0; 
           httpbeast &gt;= 0.4.2; 
           nimja 0.8.7; 
           websocket &gt;= 0.5.2; 
           websocketx &gt;= 0.1.2; 
           nimcrypto &gt;= 0.3.9
</code></pre>
<p>We can see that version <code>4.7.4</code> wasn’t selected because all of the required dependencies. We see the line <code>no versions matched requirements for the dependency: httpbeast</code> followed by the information about what version of HappyX this occurred on. It also lists all the requirements from that version of HappyX’s <a target="_blank" href="https://github.com/HapticX/happyx/blob/6298ad771ed8132ad78372e851f4789a1e34b2c4/happyx.nimble#L13">Nimble file</a>. This informs us that <code>httpbeast &gt;= 0.4.2</code> is needed to satisfy HappyX <code>4.7.4</code> requirements. BTW this httpbeast requirement is needed for pretty much every recent HappyX version.</p>
<p>Okay, now we know that we need httpbeast with a version of at least <code>&gt;= 0.4.2</code> let’s dig into why. Unfortunately the only information on httpbeast versions is:</p>
<pre><code class="lang-bash">[Notice] (atlas:resolved) [ ] (httpbeast, 0.4.0@8b50e378) 
[Notice] (atlas:resolved) [x] (httpbeast, 0.4.1@abc13d11) 
[Notice] (atlas:resolved) [ ] (httpbeast, 0.1.0@ff8fe095)
</code></pre>
<p>We need to run Atlas with higher verbosity. Let’s also grep for output about httpbeast as well as skipp HappyX info. From this output notice that we see a line <code>[Info] (httpbeast) unique versions found: 0.2.0, 0.4.1, 0.4.0, 0.3.0, 0.2.1, 0.2.2, 0.1.0</code> which gives us all the discovered httpbeast versions:</p>
<pre><code class="lang-bash">$ atlas install —verbosity:debug | grep -i httpbeast | grep -v -i happyx
[Notice] (test_figuro) Initializing packages: checksums, cligen, illwill, regex, httpx, microasynchttpserver, httpbeast, nimja, websocket, websocketx, nimcrypto, oauth, nimpy, ws 
[Info]   (httpbeast) Initializing package: https://github.com/dom96/httpbeast 
[Debug]  (httpbeast) loading dependency todo: DoNothing ondisk: /Volumes/projects/nims/test-figuro/deps/httpbeast isLinked: <span class="hljs-literal">false</span> isLazyDeferred: <span class="hljs-literal">false</span> 
[Notice] (test_figuro) Processing packages: checksums, cligen, illwill, regex, httpx, microasynchttpserver, httpbeast, nimja, websocket, websocketx, nimcrypto, oauth, nimpy, ws 
[Info]   (httpbeast) Processing package at: <span class="hljs-variable">$project</span>/deps/httpbeast 
[Debug]  (httpbeast) nimble explicit versions: @[] 
[Debug]  (httpbeast) nimble tags: @[0.4.1@abc13d11, 0.4.0@8b50e378, 0.3.0@d679e505, 0.2.2@eda2e809, 0.2.1@65e4575f, 0.2.0@2a965433, 0.1.0@ff8fe095] 
[Debug]  (httpbeast) Found new pkg: asynctools url: https://github.com/cheatfate/asynctools projectName: asynctools 
[Debug]  (httpbeast) Found new pkg: asynctools.iffy.github.com url: https://github.com/iffy/asynctools projectName: asynctools.iffy.github.com 
[Debug]  (httpbeast) Found new pkg: asynctools.timotheecour.github.com url: https://github.com/timotheecour/asynctools projectName: asynctools.timotheecour.github.com 
[Info]   (httpbeast) unique versions found: 0.2.0, 0.4.1, 0.4.0, 0.3.0, 0.2.1, 0.2.2, 0.1.0 
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.1.0@ff8fe095 : asynctools query: * 
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/cheatfate/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.2.0@2a965433 : asynctools query: * 
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/cheatfate/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.2.1@65e4575f : asynctools query: * 
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/cheatfate/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.2.2@eda2e809 : asynctools.timotheecour.github.com query: <span class="hljs-comment">#pr_fix_compilation </span>
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools.timotheecour.github.com 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/timotheecour/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.3.0@d679e505 : asynctools.iffy.github.com query: <span class="hljs-comment">#pr_fix_for_latest </span>
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools.iffy.github.com 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/iffy/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.4.0@8b50e378 : asynctools query: <span class="hljs-comment">#0e6bdc3ed5bae8c7cc9 </span>
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/cheatfate/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) checking dependency <span class="hljs-keyword">for</span>  0.4.1@abc13d11 : asynctools query: <span class="hljs-comment">#0e6bdc3ed5bae8c7cc9 </span>
[Debug]  (httpbeast) a compatible version matched requirements <span class="hljs-keyword">for</span> the dependency version: asynctools 
[Debug]  (httpbeast) version constraints <span class="hljs-keyword">for</span> requirement depdendency dep: https://github.com/cheatfate/asynctools flags:  reqsByFeatures:  
[Debug]  (httpbeast) v290 sat var: F 
[Debug]  (httpbeast) v291 sat var: F 
[Debug]  (httpbeast) v292 sat var: F 
[Debug]  (httpbeast) v293 sat var: F 
[Debug]  (httpbeast) v294 sat var: F 
[Debug]  (httpbeast) v295 sat var: F 
[Debug]  (httpbeast) v296 sat var: T 
[Debug]  (httpbeast) package satisfiable 
[Notice] (atlas:resolved) [ ] (httpbeast, 0.4.0@8b50e378) 
[Notice] (atlas:resolved) [x] (httpbeast, 0.4.1@abc13d11) 
[Notice] (atlas:resolved) [ ] (httpbeast, 0.1.0@ff8fe095) 
[Info]   (httpbeast) checkout git commit: abc13d11c210b614960fe8760e581d44cfb2e3e9 at: <span class="hljs-variable">$project</span>/deps/httpbeast 
[Warn]   (/Volumes/projects/nims/test-figuro/deps/httpbeast) could not update submodules
</code></pre>
<p>From this output we pretty quickly see that we’re only finding httpbeast versions <code>0.2.0, 0.4.1, 0.4.0, 0.3.0, 0.2.1, 0.2.2, 0.1.0</code>. We also see that httpbeast is using Git tags <code>nimble tags: @[0.4.1@abc13d11, …</code> from the output.</p>
<p><strong><em>Note</em></strong>: <em>When a Git repo has git tags Atlas uses them to determine available versions. This allows package owners to control which versions are “published” and can be used to ignore bad versions. However, since Nimble long played fast and loose with versioning, tags, etc a lot of packages aren’t always consistent.</em></p>
<p>In this case we see that httpbeast <em>doesn’t</em> list a tag for <code>4.2</code> that HappyX requires. Looking at the httpbeast repo we see it’s actually the last unreleased version. Fortunately now we know this there’s an easy fix. Change our Nimble file to:</p>
<pre><code class="lang-bash">requires <span class="hljs-string">"happyx"</span>
requires <span class="hljs-string">"httpbeast#head"</span>
</code></pre>
<p>Since the <code>requires</code> is a top level requires and it’s an explicit “version” Atlas will prefer it over and override the tagged versions. Really it’s not really a version, but <code>#head</code> will be resolved to Git commit <code>75008aab</code>.</p>
<p>Running <code>atlas install</code> again we now get <code>(atlas:resolved) [x] (happyx, 4.7.4@6298ad77)</code>! Turns out httpbeast was our only culprit. Since Atlas loads <em>every</em> version of deps it can find, the SAT solver is usually able to find a (stable) solution. When it can’t it often only takes a bit of tracking to figure out what’s going on, but Atlas usually provides all the info needed to track down most issues pretty quickly. Being verbose in the <em>right</em> places is key (and took me a week or tweaking to get right!).</p>
<pre><code class="lang-bash">...
[Notice] (atlas:resolved) inactive packages: oauth, nimpy, asynctools.iffy.github.com, asynctools.timotheecour.github.com, unicodeplus, sha1, segmentation, graphemes, timerwheel
[Notice] (atlas:resolved) selected:
[Notice] (atlas:resolved) [ ] (happyx, 4.7.3@06437f69)
[Notice] (atlas:resolved) [x] (happyx, 4.7.4@6298ad77)
[Notice] (atlas:resolved) [ ] (happyx, 0.3.0@e0ee858b)
[Notice] (atlas:resolved) [ ] (httpbeast, 0.4.1@abc13d11)
[Notice] (atlas:resolved) [x] (httpbeast, 0.4.2@75008aab^)
[Notice] (atlas:resolved) [ ] (httpbeast, 0.1.0@ff8fe095)
[Notice] (atlas:resolved) [ ] (checksums, 0.2.0@86003da1)
[Notice] (atlas:resolved) [x] (checksums, 0.2.1@f8f6bd34)
[Notice] (atlas:resolved) [ ] (checksums, 0.1.0@7ff0b762)
[Notice] (atlas:resolved) [ ] (cligen, 1.9.1@79f1566e)
[Notice] (atlas:resolved) [x] (cligen, 1.9.2@c41e6e8a)
[Notice] (atlas:resolved) [ ] (cligen, 0.9.4@231bdc9e)
[Notice] (atlas:resolved) [ ] (illwill, 0.3.0@7532f9dc)
[Notice] (atlas:resolved) [x] (illwill, 0.3.1@2fe96f5c)
[Notice] (atlas:resolved) [ ] (illwill, 0.3.2@1d12cb36)
[Notice] (atlas:resolved) [ ] (illwill, 0.2.0@fe6f670c)
[Notice] (atlas:resolved) [ ] (illwill, 0.4.1@99a120f7^)
[Notice] (atlas:resolved) [ ] (regex, 0.26.3@4593305e)
[Notice] (atlas:resolved) [x] (regex, 0.26.3@88f634b9^)
[Notice] (atlas:resolved) [ ] (regex, <span class="hljs-comment">#199e696a1b0e0db72e2e5a657926e5b944e6ae2d@199e696a)</span>
[Notice] (atlas:resolved) [ ] (httpx, 0.3.7@9fab2c3c)
[Notice] (atlas:resolved) [x] (httpx, 0.3.8@47cb5e79^)
[Notice] (atlas:resolved) [ ] (httpx, 0.1.0@4b904711)
[Notice] (atlas:resolved) [ ] (microasynchttpserver, 0.10.2@90118afb)
[Notice] (atlas:resolved) [x] (microasynchttpserver, 0.11.0@2f206514^)
[Notice] (atlas:resolved) [ ] (microasynchttpserver, 0.9.0@77a5bada)
[Notice] (atlas:resolved) [x] (nimja, 0.8.7@2ab66e65)
[Notice] (atlas:resolved) [ ] (nimja, 0.9.0@8cfb53e5)
[Notice] (atlas:resolved) [ ] (nimja, 0.10.1@b7a25c62)
[Notice] (atlas:resolved) [ ] (websocket, 0.5.1@b889d2f8)
[Notice] (atlas:resolved) [x] (websocket, 0.5.2@405f4938^)
[Notice] (atlas:resolved) [ ] (websocket, 0.1.0@a69b1afe)
[Notice] (atlas:resolved) [ ] (websocketx, 0.1.0@8c9022d4)
[Notice] (atlas:resolved) [x] (websocketx, 0.1.2@b0ea475e^)
[Notice] (atlas:resolved) [ ] (nimcrypto, 0.7.1@55275b31)
[Notice] (atlas:resolved) [x] (nimcrypto, 0.7.2@3fab7f9d)
[Notice] (atlas:resolved) [ ] (nimcrypto, 0.6.0@b591f34d)
[Notice] (atlas:resolved) [ ] (ws, 0.4.4@13cbd439)
[Notice] (atlas:resolved) [x] (ws, 0.5.0@9536bf99)
[Notice] (atlas:resolved) [ ] (ws, 0.2.3@10fa5edf)
[Notice] (atlas:resolved) [x] (asynctools, <span class="hljs-comment">#0e6bdc3ed5bae8c7cc9e03cfbf66b7c882a908a7@0e6bdc3e)</span>
[Notice] (atlas:resolved) [ ] (asynctools, 0.1.0@809a836e)
[Notice] (atlas:resolved) [ ] (asynctools, 0.1.1@a314fb8d)
[Notice] (atlas:resolved) [ ] (unicodedb, 0.13.1@3f8b524f)
[Notice] (atlas:resolved) [x] (unicodedb, 0.13.2@66f24587^)
[Notice] (atlas:resolved) [ ] (unicodedb, 0.1@625b0d11)
[Notice] (atlas:resolved) [ ] (ioselectors, 0.1.8@6fcdbf3f)
[Notice] (atlas:resolved) [x] (ioselectors, 0.2.0@c9344036^)
[Notice] (atlas:resolved) [ ] (ioselectors, 0.1.0@ef5db4a3)
[Notice] (atlas:resolved) [ ] (picohttpparser, 0.10.0@5248bd34)
[Notice] (atlas:resolved) [x] (picohttpparser, 0.11.0@2e1e6d9c^)
[Notice] (atlas:resolved) [ ] (picohttpparser, 0.9.0@e7bc46f8)
[Notice] (atlas:resolved) [ ] (wepoll, 0.1.0@c49c3280)
[Notice] (atlas:resolved) [x] (wepoll, 0.1.2@07fb549e^)
[Notice] (atlas:resolved) end of selection
...
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Signals and Slots for IoT: Calm, Caffeinated Concurrency with Nim and Sigils]]></title><description><![CDATA[Sigils has been a fun little project I created to implement QT's famous signals and slots mechanism in Nim. Initially it was created for working with my experimental UI library Figuro but it has started to get a life of its own.
Signals and slots are...]]></description><link>https://blog.elcritch.net/signals-and-slots-for-iot-calm-caffeinated-concurrency-with-nim-and-sigils</link><guid isPermaLink="true">https://blog.elcritch.net/signals-and-slots-for-iot-calm-caffeinated-concurrency-with-nim-and-sigils</guid><category><![CDATA[iot]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[Threads]]></category><category><![CDATA[Nim]]></category><category><![CDATA[signals]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Wed, 24 Sep 2025 15:57:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RQsmFAz8VCE/upload/61925e01284ced57bf63e08ca997dafc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/elcritch/sigils">Sigils</a> has been a fun little project I created to implement <a target="_blank" href="https://doc.qt.io/qt-6/signalsandslots.html">QT's famous signals and slots</a> mechanism in <a target="_blank" href="https://nim-lang.org/">Nim</a>. Initially it was created for working with my experimental UI library <a target="_blank" href="https://github.com/elcritch/figuro">Figuro</a> but it has started to get a life of its own.</p>
<p><em>Signals and slots</em> are a powerful programming construct that lends itself to implementing observer patterns, bus architectures, and producer-consumer patterns. The last one lends itself to supporting common multi-threading patterns!</p>
<p>After adding threading support to Sigils this spring it dawned on me: <em>signals and slots</em> delivered a very useful set of core features: message passing oriented, event driven, fully typed, and pleasantly thread-savvy. Everything needed for implementing actors?!</p>
<p>You see, for a long I’ve wanted something closer to Elixir and Erlang for Nim where self-contained and preemptive actors are easy to create and use. Unfortunately they’re harder to replicate in a natively compiled language. Go doesn’t do it, neither does async.</p>
<p>Combine threaded <em>signal and slots</em> with an easy way to spawn up a new thread for an object and bam you got a preemptive actor system! Now some say we’re not using “lightweight” threads or some such, but who cares when we can run <a target="_blank" href="https://justine.lol/cosmo3/#web">9,000+ threads</a> on moderate Linux servers. Smaller number of threaded actors can be very useful for <a target="_blank" href="https://www.state-machine.com/products/qp">embedded real time devices</a> as well!</p>
<h2 id="heading-signals-and-slots">Signals and Slots</h2>
<p>What are <em>signals and slots</em>? If you haven’t used QT before you may be unfamiliar with them. QT introduces them like this:</p>
<blockquote>
<p>In GUI programming, when we change one widget, we often want another widget to be notified. More generally, we want objects of any kind to be able to communicate with one another. For example, if a user clicks a <strong>Close</strong> button, we probably want the window's <a target="_blank" href="https://doc.qt.io/qt-6/qwidget.html#close"><strong>close</strong>() fu</a>nction to be called.</p>
<p>Other toolkits achieve this kind of communication using callbacks. A callback is a pointer to a function, so if you want a processing function to notify you about some event you pass a pointer to another function (the callback) to the processing function. The p<a target="_blank" href="https://doc.qt.io/qt-6/qwidget.html#close">roces</a>sing function then calls the callback when appropriate. While successful frameworks using this method do exist, callbacks can be unintuitive and may suffer from problems in ensuring the type-correctness of callback arguments.</p>
<p>In Qt, we have an alternative to the callback technique: We use signals and slots. A signal is emitted when a particular event occurs. Qt's widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a function that is called in response to a particular signal. Qt's widgets have many pre-defined slots, but it is common practice to subclass widgets and add your own slots so that you can handle the signals that you are interested in.</p>
</blockquote>
<p>You can read more in the <a target="_blank" href="https://doc.qt.io/qt-6/signalsandslots.html">QT Documentation</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/Signals_and_slots"><code>Wikipedia</code></a>.</p>
<p>The core feature of <em>signals and slots</em> lies in decoupling. Interactions become messages, or, events. From this you can begin modeling systems as connected state machines. It a bit of an art to learn how to use them effectively.</p>
<h2 id="heading-backstory-iot-project-quagmire">Backstory: IoT Project Quagmire</h2>
<p>For the past few months I’ve been building out an IoT project for a startup using Nim, which has been a “superpower” for my IoT work. Some of the issues I've encountered would've been much harder to solve without it.</p>
<p>The IoT project started with just a simple sensor but unfortunately got more complicated as we had to work around the software limitations of a vendor's hardware module that we rely on. This made the problem an order of magnitude more complex.</p>
<p>This complicated the data handling significantly and the original codebase meant as a quick and dirty prototype had to evolve. Early on I had introduced a simple worker thread with a shared object protected via a lock. This was simple and mostly worked.</p>
<p>However it became a bit of a mess trying to ensure that the data from the database, vendor’s cloud API, and our own sensors were being coordinated nicely. It was hard to understand or modify.</p>
<p>I decided to try using my Sigils library in a new database syncing module. I’d done a bunch of work making it thread-safe this spring and it’b been solid and made threading easy. The experiment worked very well.</p>
<p>Now I've rewritten most of it to use Sigils between components and it has drastically simplified the architecture. At this points it's almost 7,000 lines of code so it's not “tiny”. Making new features and fixing issue is drastically easier now.</p>
<h2 id="heading-using-signals-and-slots-in-nim">Using Signals and Slots in Nim</h2>
<p><a target="_blank" href="https://github.com/elcritch/sigils">Sigils</a> is based on idea of events and message passing. It’s a form of object oriented programming, but shares a heritage more with SmallTalk and Object-C than Java and C++.</p>
<p>At it’s core it is pretty simple:</p>
<pre><code class="lang-ruby">import sigils

type Counter*[T] = ref object of Agent
       <span class="hljs-symbol">value:</span> T

proc valueChanged*[T](<span class="hljs-symbol">tp:</span> Counter[T], <span class="hljs-symbol">val:</span> T) {.signal.}

proc setValue*[T](<span class="hljs-symbol">self:</span> Counter[T], <span class="hljs-symbol">value:</span> T) {.slot.} =
  echo <span class="hljs-string">"setValue! "</span>, value
  <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.value != <span class="hljs-symbol">value:</span>
    <span class="hljs-comment"># we want to be careful not to set circular triggers!</span>
    <span class="hljs-keyword">self</span>.value = value
    emit <span class="hljs-keyword">self</span>.valueChanged(value)
</code></pre>
<p>That’s it. However, you add in threading and suddenly you begin get concurrency and separation of concerns. Under the hood that <code>emit self.valuedChanged(value)</code> doesn’t care if it’s invoking a native proc, or a remote proc.</p>
<h2 id="heading-why-signals-and-slots-work-well-in-nim-projects">Why Signals and Slots Work well in Nim Projects</h2>
<ul>
<li><p>Decoupled by default: producers and consumers meet at events, not internals.</p>
</li>
<li><p>Typed contracts: <code>.signal.</code> and <code>.slot.</code> capture intent you can refactor fearlessly.</p>
</li>
<li><p>Ownership-aware concurrency: slots run on their agent’s thread; shared state stays sane.</p>
</li>
<li><p>Intentional wiring: <code>connect</code> reads like a map of the system — grepable topology.</p>
</li>
<li><p>Cross‑thread, zero drama: <code>Agent</code>, <code>AgentProxy</code>, <code>moveToThread</code>, and <code>emit</code> make multi-thread fan-out/fan-in feel frictionless.</p>
</li>
</ul>
<p>Calm beats clever. Sigils keeps the caffeine without the jitters.</p>
<h2 id="heading-a-small-generic-example-nim-sigils">A Small, Generic Example (Nim + Sigils)</h2>
<p>Imagine a sensor pipeline: a <code>Sensor</code> emits readings, a <code>Projector</code> updates views for a UI, and a <code>Processor</code> enriches data. Three agents, two threads, one readable wiring harness.</p>
<pre><code class="lang-ruby">import sigils
import sigils/threads

type
  Temperature = object
    <span class="hljs-symbol">celsius:</span> float
    <span class="hljs-symbol">ts:</span> int64

  Sensor = ref object of Agent
  Projector = ref object of Agent
  Processor = ref object of Agent
  Boot = ref object of Agent

proc start(<span class="hljs-symbol">self:</span> Boot) {.signal.}
proc reading(<span class="hljs-symbol">self:</span> Sensor, <span class="hljs-symbol">t:</span> Temperature) {.signal.}

proc onReading(<span class="hljs-symbol">self:</span> Projector, <span class="hljs-symbol">t:</span> Temperature) {.slot.} =
  discard  <span class="hljs-comment"># update in-memory view / notify UI</span>

proc onProcess(<span class="hljs-symbol">self:</span> Processor, <span class="hljs-symbol">t:</span> Temperature) {.slot.} =
  discard  <span class="hljs-comment"># persist, aggregate, or enrich</span>

proc init(<span class="hljs-symbol">self:</span> Sensor) {.slot.} =
  let tProj = newSigilThread(); tProj.start()
  let tProc = newSigilThread(); tProc.start()

  let projector = Projector().moveToThread(tProj)
  let processor = Processor().moveToThread(tProc)

  <span class="hljs-comment"># Fan-out one signal to many slots, across threads</span>
  threads.connect(<span class="hljs-keyword">self</span>, reading, projector, onReading)
  threads.connect(<span class="hljs-keyword">self</span>, reading, processor, onProcess)

  <span class="hljs-comment"># Kick off a first reading; later, timers/IO would emit more</span>
  emit <span class="hljs-keyword">self</span>.reading(Temperature(<span class="hljs-symbol">celsius:</span> <span class="hljs-number">21.5</span>, <span class="hljs-symbol">ts:</span> <span class="hljs-number">1690000000</span>))

proc run() =
  let tSensor = newSigilThread(); tSensor.start()
  let sensor = Sensor().moveToThread(tSensor)
  let boot = Boot()
  threads.connect(boot, start, sensor, init)
  emit boot.start()
</code></pre>
<p>Readability is the feature. Who emits? Who listens? Which thread owns what? It’s all in one place, with types you can lean on. You can use this to encapsulate data while orchestrating events from a single point.</p>
<p>Meanwhile a <code>signal</code> can target any agent or a very specific agent avoiding the need for complex object hierarchies or creating one off traits or interfaces. You gain granularity. Now there is the downside that groups of methods aren’t modeled but we can lean on Nim’s concepts for that if we want.</p>
<p>Since <code>connect</code> can tie a single event to multiple <code>slot</code>'s you get free bus and fan-out even across threads. You can readily connect another <code>slot</code> just to debug and log an event.</p>
<h2 id="heading-queued-connections-and-loops">Queued Connections and Loops</h2>
<p>Another useful pattern is simple looping for batching work (better timers are coming to Sigils soon!):</p>
<pre><code class="lang-ruby">import sigils
import sigils/threads
import std/times

type
  Processor = ref object of Agent

proc start(<span class="hljs-symbol">self:</span> Processor) {.signal.}
proc update(<span class="hljs-symbol">self:</span> Processor, <span class="hljs-symbol">lastTs:</span> DateTime) {.signal.}

proc onUpdate(<span class="hljs-symbol">self:</span> Processor, <span class="hljs-symbol">lastTs:</span> DateTime) {.slot.} =
  echo <span class="hljs-string">"Starting from: "</span>, lastTs
  discard  <span class="hljs-comment"># do more processing</span>
  emit <span class="hljs-keyword">self</span>.update(now())

proc init(<span class="hljs-symbol">self:</span> Sensor) {.slot.} =
  <span class="hljs-comment"># Whenever `update` is emitted it's put onto the internal thread queue</span>
  connectQueued(<span class="hljs-keyword">self</span>, processor, onUpdate, update)
  emit <span class="hljs-keyword">self</span>.update(loadLastTimeFromDb())

proc run() =
  let tProc = newSigilThread(); tProc.start()
  let processorProxy = Processor().moveToThread(tProc)
  threads.connect(processorProxy, start, processorProxy, init)
  emit processorProxy.start()
</code></pre>
<h2 id="heading-actors-airlocks-and-alliterations">Actors, Airlocks, and Alliterations</h2>
<p>Signals &amp; slots bears similarities to a few great patterns:</p>
<ul>
<li><p>Actors: Each <code>Agent</code> acts like an actor — single-threaded in spirit, stateful by design. Slots are the airlocks where messages meet mutation.</p>
</li>
<li><p>Event sourcing adjacent: Signals are domain events you can journal. Want replay? persist at emit time or inside a slot.</p>
</li>
<li><p>CQRS without contortions: Use signals for commands (do this) and slots to project read models (show that). Writes stay focused; reads stay fast.</p>
</li>
</ul>
<p>Pragmatic patterns, not purist prescriptions. The architecture breathes as the system grows.</p>
<h2 id="heading-threading-model-made-mild">Threading Model, Made Mild</h2>
<ul>
<li><p><code>Agent</code> and <code>AgentProxy</code> clarify ownership and routes.</p>
</li>
<li><p><code>moveToThread</code> pins state to a thread; invariants stay local.</p>
</li>
<li><p><code>.slot.</code> runs where the state lives — no surprise sharing.</p>
</li>
<li><p><code>.signal.</code> + <code>emit</code> give you typed, async messaging.</p>
</li>
<li><p><code>threads.connect</code> is the wiring harness you can reason about at a glance.</p>
</li>
</ul>
<p>Use shared lockers only when necessary; let agents own their data and their destiny.</p>
<h2 id="heading-production-playbook">Production Playbook</h2>
<ul>
<li><p>Name domain events, not mechanics: <code>readingReady</code> beats <code>evt42</code>.</p>
</li>
<li><p>Co-locate connections: put the wiring where the components are born.</p>
</li>
<li><p>Idempotency everywhere: expect retries, reorders, and late arrivals.</p>
</li>
<li><p>Backpressure by boundaries: slow consumers don’t stall producers.</p>
</li>
<li><p>Fail fast, log well, restart small: treat agents like OTP processes.</p>
</li>
</ul>
<h2 id="heading-where-it-shines-and-when-to-add-more">Where It Shines (and When to Add More)</h2>
<ul>
<li><p>Shines for:</p>
<ul>
<li><p>Multi-thread orchestration with crisp, typed boundaries.</p>
</li>
<li><p>Fanning out domain events to logs, views, and analytics.</p>
</li>
<li><p>Evolving systems incrementally: add listeners, swap implementations, keep calm.</p>
</li>
</ul>
</li>
<li><p>Add layers when:</p>
<ul>
<li><p>You need durable, replayable event logs — append events at emit/slot edges.</p>
</li>
<li><p>You want tree-structured supervision — introduce lifecycle helpers and restarts.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-caveats">Caveats</h2>
<p>Sigils support for threading is still young. Even during this project there’s a few features I’ve wanted. Items like forwarding a signal from one agent to another without needing to make a new slot, or connecting signal and slots directly between two remote threads, etc. Better timers is high on my list.</p>
<p>Sigils supports <code>std/async</code> but it’s a CPU hog due to limits in std/asyncdispatch API. Perhaps I’ll have to add Chronos support later.</p>
<h2 id="heading-closing-notes-from-an-otp-alum">Closing Notes from an OTP Alum</h2>
<p>Years of Elixir taught me to cherish explicit boundaries, restartable units, and the quiet confidence of message passing. [Sigils](<a target="_blank" href="https://github.com/elcritch/sigils">https://github.com/elcritch/sigils</a>) for Nim lets me keep that mindset without fighting the language. Signals, slots, and steady systems: a friendly trio for IoT that stays fast, focused, and surprisingly fun.</p>
<p>If you want to push further, try:</p>
<ul>
<li><p>A tiny event log for a handful of high-value signals.</p>
</li>
<li><p>A minimal supervision helper (start/monitor/restart) for agents.</p>
</li>
<li><p>Slot-level telemetry (queue depth, handler time) to catch slowdowns early.</p>
</li>
</ul>
<p>Signals, slots, and serene systems — composable, comprehensible, and quietly quick.</p>
]]></content:encoded></item><item><title><![CDATA[Run Nim Code Anywhere with Cosmopolitan]]></title><description><![CDATA[A few years back a somewhat crazy fellow made [Cosmopolitan](https://github.com/jart/cosmopolitan)\:

Cosmopolitan Libc makes C/C++ a build-once run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it r...]]></description><link>https://blog.elcritch.net/run-nim-code-anywhere-with-cosmopolitan</link><guid isPermaLink="true">https://blog.elcritch.net/run-nim-code-anywhere-with-cosmopolitan</guid><category><![CDATA[cross platfom]]></category><category><![CDATA[cross-compile]]></category><category><![CDATA[Nim]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Mon, 11 Aug 2025 07:47:57 GMT</pubDate><content:encoded><![CDATA[<p>A few years back a somewhat crazy fellow made [<a target="_blank" href="[https://github.com/jart/cosmopolitan](https://github.com/jart/cosmopolitan)">Cosmopolitan](https://github.com/jart/cosmopolitan)\</a>:</p>
<blockquote>
<p><a target="_blank" href="https://justine.lol/cosmopolitan/index.html">Cosmopolitan Libc</a> <a target="_blank" href="https://justine.lol/cosmopolitan/index.html">makes C/C++ a bu</a>ild-once run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it reconfigures stock GCC and Clang to output a POSIX-approved polyglot format that runs natively on Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the best possible performance and the tiniest footprint imaginable.</p>
</blockquote>
<p>It’s built on some crazy hacks. Yet surprisingly it works way better than it should. It went from a [crazy idea](<a target="_blank" href="https://justine.lol/ape.html">https://justine.lol/ape.html</a>) to a stable project. Rumor has it that companies actually use it!</p>
<p>For a long time I’ve wanted to try using it with Nim to make a truly portable executable. I get annoyed having to do the whole shell script dance to find the right OS and architecture for a binary:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Detect OS - but wait, is it "linux" or "Linux"? "darwin" or "macos"?</span>
<span class="hljs-keyword">case</span> <span class="hljs-string">"<span class="hljs-subst">$(uname -s)</span>"</span> <span class="hljs-keyword">in</span>
    Linux*)  OS=<span class="hljs-string">"linux"</span> ;;
    Darwin*) OS=<span class="hljs-string">"darwin"</span> ;;  <span class="hljs-comment"># or is it "macos"? "osx"?</span>
    *)       OS=<span class="hljs-string">"windows"</span> ;;
<span class="hljs-keyword">esac</span>

<span class="hljs-comment"># Detect arch - amd64? x86_64? x64? 64-bit?</span>
<span class="hljs-keyword">case</span> <span class="hljs-string">"<span class="hljs-subst">$(uname -m)</span>"</span> <span class="hljs-keyword">in</span>
    x86_64) ARCH=<span class="hljs-string">"amd64"</span> ;;  <span class="hljs-comment"># or is it "x86_64"? "x64"?</span>
    arm64)  ARCH=<span class="hljs-string">"arm64"</span> ;;  <span class="hljs-comment"># or "aarch64"?</span>
    *)      ARCH=<span class="hljs-string">"386"</span> ;;
<span class="hljs-keyword">esac</span>

...
</code></pre>
<h2 id="heading-compiling-nim-code-with-cosmopolitan">Compiling Nim Code with Cosmopolitan</h2>
<p>The instructions for Cosmopolitan are pretty simple:</p>
<pre><code class="lang-bash">mkdir -p cosmocc
<span class="hljs-built_in">cd</span> cosmocc
wget https://cosmo.zip/pub/cosmocc/cosmocc.zip
unzip cosmocc.zip
cosmocc -o hello hello.c
./hello
</code></pre>
<p>Plugging it into Nim took a bit of experimenting however. The C compiler configurations for Nim have always been a bit murky to me. Mostly I never have to bother with them. In this case we need to adjust the target OS for Nim, along with the target arch and C libraries.</p>
<p>After a bit of tinkering I found a simple way to setup the compiler by setting a simple <code>-d:cosmopolatin</code> flag:</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># mycode.nim.cfg</span>
@if <span class="hljs-symbol">cosmopolitan:</span>
  <span class="hljs-comment"># ARM64/aarch64 e.g. Raspberry Pi 3: gcc-aarch64-linux-gnu package on Debian/Ubuntu</span>
  os = <span class="hljs-string">"linux"</span>
  cc = <span class="hljs-string">"gcc"</span>
  arm64.linux.gcc.exe = <span class="hljs-string">"cosmocc"</span>
  arm64.linux.gcc.linkerexe = <span class="hljs-string">"cosmocc"</span>
  amd64.linux.gcc.exe = <span class="hljs-string">"cosmocc"</span>
  amd64.linux.gcc.linkerexe = <span class="hljs-string">"cosmocc"</span>
  gcc.options.always = <span class="hljs-string">"-static -fno-pie -no-pie "</span>
@else:
  --<span class="hljs-symbol">define:</span>ssl
@end
</code></pre>
<p>I tried this with [Atlas](<a target="_blank" href="https://github.com/nim-lang/atlas">https://github.com/nim-lang/atlas</a>) because I like the idea of just downloading a universal Atlas and have it ready to run on IoT devices, CIs, or whatnot. First I added the above <code>nim.cfg</code> to <code>atlas.nim.cfg</code> then ran:</p>
<pre><code class="lang-bash">-&gt; % nim c -d:release --verbosity:3 -d:cosmopolitan -o:bin/atlas-cosmo src/atlas.nim
...
Hint: mm: orc; threads: on; opt: speed; options: -d:release
133181 lines; 2.017s; 341.906MiB peakmem; proj: /Volumes/projects/nims/atlas/src/atlas.nim; out: /Volumes/projects/nims/atlas/bin/atlas-cosmo [SuccessX]
/Users/elcritch/.<span class="hljs-built_in">local</span>/share/grabnim/nim-2.2.4/compiler/msgs.nim(716, 13) compiler msg initiated here [MsgOrigin]
[GC] total memory: 358514688
[GC] occupied memory: 316239632
-&gt; % ls -lh bin/                                                                     
total 30712
-rwxr-xr-x  1 elcritch  staff   4.5M Aug 11 01:28 atlas-cosmo
-rwx--x--x  1 elcritch  staff   4.4M Aug 11 01:28 atlas-cosmo.aarch64.elf
-rwx--x--x  1 elcritch  staff   5.3M Aug 11 01:28 atlas-cosmo.com.dbg
</code></pre>
<p>Boom a 4.5 mb binary that runs seamlessly on my ARM64 Macbook running MacOS or on an x86_64 IoT device I’m working on! It does also run on Windows, but the path handling would need to be tweaked to handle it since Atlas relies on compile time switching for windows paths.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Am I going to use this? I don’t know.</p>
<p>It’s a very impressive that it works at all but there’s some dark magic going on to do so. However, it’s at release v4 now and been around for years! It’s also impressive how simple it was to setup once I got the right compiler settings for Nim. Much easier than 99% of cross compilation I’ve done before.</p>
<p>So perhaps it’ll help in some of those cases instead of making a multi-platform docker image and that set of crap, you can make a universal Nim binary instead. I’ve wasted far too many hours of my life wanting to get the right set of commands to get a simple binary to cross compile and wading through Github CI issues or Docker OCI container rabbit holes.</p>
]]></content:encoded></item><item><title><![CDATA[CSSGrid Engine Updates (april 2025)]]></title><description><![CDATA[A while back I’d made some big update for [CSSGrid](https://github.com/elcritch/cssgrid) library.
Here's a sample using justify and alignment along with auto-flow algorithm in CSS Grid. The left and right most nodes are fixed in place, while the rest...]]></description><link>https://blog.elcritch.net/cssgrid-engine-updates-april-2025</link><guid isPermaLink="true">https://blog.elcritch.net/cssgrid-engine-updates-april-2025</guid><category><![CDATA[CSS Grid]]></category><category><![CDATA[UI]]></category><category><![CDATA[library]]></category><category><![CDATA[Nim]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Mon, 11 Aug 2025 07:14:39 GMT</pubDate><content:encoded><![CDATA[<p>A while back I’d made some big update for [CSSGrid](<a target="_blank" href="https://github.com/elcritch/cssgrid">https://github.com/elcritch/cssgrid</a>) library.</p>
<p>Here's a sample using justify and alignment along with auto-flow algorithm in CSS Grid. The left and right most nodes are fixed in place, while the rest of the nodes are positioned dynamically. The second row of elements adjust their justify or align properties.</p>
<p><img src="https://github.com/elcritch/cssgrid/raw/main/tests/tlayout-grid-align-and-justifygrRow-expected.png" alt /></p>
<p>It's a pretty big update:</p>
<ul>
<li><p>basic html/css style layout for non-grid nodes using computeLayout taken from Figuro</p>
</li>
<li><p>basic html layout using constraints like percent parent (<code>20'pp</code>), auto (<code>cx"auto"</code>), content-min (<code>cx"content-min"</code>)</p>
</li>
<li><p>support for constraint operators including min, max, - and + (limited to single level, e.g. no <code>min(min(x,y),z))</code></p>
</li>
<li><p>basic and grid layouts now support min-size and max-size constraints (e.g. like CSS min-width)</p>
</li>
<li><p>fixed justify and align bugs and gotchas for CSS Grids</p>
</li>
<li><p>lots more test coverage</p>
</li>
<li><p>the core UiScalar type is configurable to support float32, float64, int, int32, int64 by using a compile flag (great for embedded or terminal projects!)</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[130 Pages of CSS Grid Spec and Claude-3.7]]></title><description><![CDATA[The Challenge
The other night I got an itch and spent many hours trying to coax LLMs in Cursor to rewrite my home-brew css grid implementation to match the css grid level 2 spec. See my CSS Grid implementation in Nim started from the layout code in F...]]></description><link>https://blog.elcritch.net/130-pages-of-css-grid-spec-and-claude-37</link><guid isPermaLink="true">https://blog.elcritch.net/130-pages-of-css-grid-spec-and-claude-37</guid><category><![CDATA[GUI]]></category><category><![CDATA[llm]]></category><category><![CDATA[Nim]]></category><category><![CDATA[CSS Grid]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[CSS]]></category><category><![CDATA[cursor]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Mon, 31 Mar 2025 03:07:59 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-the-challenge">The Challenge</h2>
<p>The other night I got an itch and spent many hours trying to coax LLMs in Cursor to rewrite my home-brew <a target="_blank" href="https://github.com/elcritch/cssgrid">css grid implementation</a> to match the <a target="_blank" href="https://www.w3.org/TR/css-grid-2/">css grid level 2 spec</a>. See my CSS Grid implementation in <a target="_blank" href="https://nim-lang.org/">Nim</a> started from the layout code in <a target="_blank" href="https://github.com/treeform/fidget">Fidget</a> and my attempts to add CSS Grid to it. It resulted in a weird codebase that worked surprisingly well but definitely missed the spec. Growing it to handle things like <code>min</code> or <code>minmax</code> in the grid was becoming more painful.</p>
<p>From previous experience I found that LLMs are fantastic at translating code but need a lot more hand holding to write new code from scratch. The CSS Grid Level 2 spec is a 100+ page spec with parts specifying the grid layout algorithm. It felt like an ideal testing ground for this theory.</p>
<p>My first prompt:</p>
<pre><code class="lang-markdown">Please translate the algorithm described in <span class="hljs-code">`css-grid-level-2.md`</span> into Nim code 
using the style and base functions in <span class="hljs-code">`layout.nim`</span> and <span class="hljs-code">`basiclayout.nim`</span>. Follow 
the spec if they disagree but try to re-use <span class="hljs-code">`basiccalcs.nim`</span> functions if possible.
</code></pre>
<p>The results of my first try spurred on my enthusiasm. After carefully analyzing the implementation and thinking for longer than it had ever before, it produced some decent looking code and a well thought out plan based on the CSS Grid Spec and my codebase:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743382722391/dce02755-7473-4a65-9db2-7559df107c31.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-results">Results</h2>
<p>Well that was awesome and much better than I had anticipated. It even re-used some of my existing algorithms for placing grid tracks and auto-flowing nodes.</p>
<p>The code went from my code:</p>
<pre><code class="lang-ruby">proc computeLineLayout*(<span class="hljs-symbol">lines:</span> var seq[GridLine]; <span class="hljs-symbol">dir:</span> GridDir, <span class="hljs-symbol">computedSizes:</span> Table[int, ComputedTrackSize],
                        <span class="hljs-symbol">length:</span> UiScalar, <span class="hljs-symbol">spacing:</span> UiScalar) =
  var
    fixed = <span class="hljs-number">0</span>.UiScalar
    totalFracs = <span class="hljs-number">0</span>.<span class="hljs-number">0</span>.UiScalar
    totalAuto = <span class="hljs-number">0</span>.<span class="hljs-number">0</span>.UiScalar

  <span class="hljs-comment"># First pass: calculate fixed sizes and identify auto/frac tracks</span>
  <span class="hljs-keyword">for</span> i, grdLn <span class="hljs-keyword">in</span> lines.mpairs():
    <span class="hljs-keyword">case</span> grdLn.track.<span class="hljs-symbol">kind:</span>
      of <span class="hljs-symbol">UiValue:</span>
        let value = grdLn.track.value
        <span class="hljs-keyword">case</span> value.<span class="hljs-symbol">kind:</span>
          of UiFixed, UiPerc, UiContentMin, UiContentMax, <span class="hljs-symbol">UiContentFit:</span>
            grdLn.width = processUiValue(value, i, computedSizes, totalAuto, totalFracs, length)
            fixed += grdLn.width
          of UiFrac, <span class="hljs-symbol">UiAuto:</span>
            grdLn.width = processUiValue(value, i, computedSizes, totalAuto, totalFracs, length)
      of UiMin, UiMax, UiAdd, UiSub, <span class="hljs-symbol">UiMinMax:</span>
        let args = cssFuncArgs(grdLn.track)
        let lv = processUiValue(args.l, i, computedSizes, totalAuto, totalFracs)
        let rv = processUiValue(args.r, i, computedSizes, totalAuto, totalFracs)
        grdLn.width = computeCssFuncs(grdLn.track.kind, lv, rv)
      <span class="hljs-symbol">else:</span>
        debugPrint <span class="hljs-string">"computeLineLayout:unknown: "</span>, <span class="hljs-string">"track="</span>, grdLn.track
</code></pre>
<p>to the code Claude gave me that started looking like it knew what it was doing rather than having a <em>liver that metastasized into a brain</em> as my code and some galactic species have had:</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># Grid Layout Algorithm implementation following CSS Grid Level 2 spec</span>
proc runGridLayoutAlgorithm*(<span class="hljs-symbol">node:</span> GridNode) =
  <span class="hljs-comment">## Implementation of the grid layout algorithm as defined in css-grid-level-2.md</span>
  <span class="hljs-comment">## 1. Run the Grid Item Placement Algorithm</span>
  <span class="hljs-comment">## 2. Find the size of the grid container</span>
  <span class="hljs-comment">## 3. Run the Grid Sizing Algorithm</span>
  <span class="hljs-comment">## 4. Lay out the grid items</span>

  <span class="hljs-comment"># 1. Run the Grid Item Placement Algorithm</span>
  <span class="hljs-comment"># 1a. First, handle auto flow items - this is what was missing</span>
  <span class="hljs-comment"># Check if there are any items that need auto flow</span>
  var hasAutos = <span class="hljs-literal">false</span>
  <span class="hljs-keyword">for</span> child <span class="hljs-keyword">in</span> node.<span class="hljs-symbol">children:</span>
    <span class="hljs-keyword">if</span> child.gridItem == <span class="hljs-symbol">nil:</span>
      child.gridItem = GridItem()
    child.gridItem.setGridSpans(node.gridTemplate, child.box.wh.UiSize)

    <span class="hljs-comment"># If this item doesn't have all positions set, we need auto flow</span>
    <span class="hljs-keyword">if</span> fixedCount(child.gridItem) != <span class="hljs-number">4</span>:
      hasAutos = <span class="hljs-literal">true</span>

  <span class="hljs-comment"># Run auto flow placement if needed</span>
  <span class="hljs-keyword">if</span> <span class="hljs-symbol">hasAutos:</span>
    debugPrint <span class="hljs-string">"runGridLayoutAlgorithm:computeAutoFlow"</span>
    computeAutoFlow(node.gridTemplate, node.box, node.children)

  <span class="hljs-comment"># 1b. Now set final spans with the positions</span>
  <span class="hljs-keyword">for</span> child <span class="hljs-keyword">in</span> node.<span class="hljs-symbol">children:</span>
    child.gridItem.setGridSpans(node.gridTemplate, child.box.wh.UiSize)
</code></pre>
<h2 id="heading-then-problems">Then Problems</h2>
<p>Unfortunately after iterating through several rounds of refinement a few persistent test failures remained elusive. So I figured it was stuck and no way I wanted to dive into the pile of code that looked just as confusing as the original spec.</p>
<p>I figured, surely, it must be my original codebase hindering the LLM. I mean look at all that analysis following the in depth specification.</p>
<h2 id="heading-exploring-different-approaches">Exploring Different Approaches</h2>
<p>Determined to solve these issues, I shifted strategies and tried different models. They generated new code from scratch only using my basic data types. However each iteration seemed to get <em>further</em> from the goal.</p>
<p>Well, I tried o1-mini, o1, and Claude 3.5, Claude 3.7, Claude 3.7 Max - asking each to generate a Nim implementation from scratch using just my core data types. As Johnny Cash said, I’ve been every where man.</p>
<p>Each model spent considerable time thinking and produced code that looked similar to my first attempt with Claude. At first at least. None fully passed my test suite. In fact they were even far worse as none of the test suite passed.</p>
<p>Then the models apparently just got <em>tired</em> of working on my problem. They just started changing random comments, even after I started a new chat to try a different tack. They just started giving me crap:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743383406739/ecfd9cb1-c64d-44fc-9278-f91b61083249.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-resolution-and-logging">The Resolution and Logging</h2>
<p>After hours of going in circles, I nearly abandoned the project. I had somewhat working code after-all.</p>
<p>However, after spending hours on this and feeling kinda frustrated I decided to give it one more go despite it getting late. I’d go back to the original version another try.</p>
<p>At first it was the same story. But I'd started putting logging output from the failed tests into the prompt context. It was an effort to get the LLM to focus on the actual issue. With the de novo code, it didn’t work. I suspect it was just too far from a working solution for the LLM to be able to find a path toward.</p>
<p>My first take however had the LLM build more on my existing codebase where I’d done substantial work getting some of the basics working. Some of that followed the spec’s algorithm and worked pretty well already. Especially elements like tying in my Node elements into the sized grid, etc.</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># Adding strategic debug logging to visualize the algorithm steps:</span>
debugPrint <span class="hljs-string">"trackSizingAlgorithm:start"</span>, <span class="hljs-string">"dir="</span>, dir, <span class="hljs-string">"availableSpace="</span>, availableSpace
debugPrint <span class="hljs-string">"expandFlexibleTracks"</span>, <span class="hljs-string">"dir="</span>, dir, <span class="hljs-string">"availableSpace="</span>, availableSpace, 
           <span class="hljs-string">"nonFlexSpace="</span>, nonFlexSpace, <span class="hljs-string">"flexSpace="</span>, flexSpace,
           <span class="hljs-string">"totalFlex="</span>, totalFlex, <span class="hljs-string">"frUnitValue="</span>, frUnitValue
</code></pre>
<p>Now when I added logging output (similar to what I've used many times before to find bugs), the model's understanding suddenly improved. I removed unrelated logging that was creating noise, and progress accelerated dramatically. The solution began to take shape. Now I’m left with just one oddball test only failing on a few minor points.</p>
<h2 id="heading-key-technical-improvements">Key Technical Improvements</h2>
<p>The new implementation follows the CSS Grid Layout Algorithm much more closely, particularly:</p>
<ol>
<li><strong>Track Sizing Algorithm</strong>: Now implemented as a series of phases that match the specification:</li>
</ol>
<pre><code class="lang-ruby">proc trackSizingAlgorithm*(
    <span class="hljs-symbol">grid:</span> GridTemplate,
    <span class="hljs-symbol">dir:</span> GridDir, 
    <span class="hljs-symbol">trackSizes:</span> Table[int, ComputedTrackSize],
    <span class="hljs-symbol">availableSpace:</span> UiScalar
) =
  <span class="hljs-comment"># 1. Initialize Track Sizes</span>
  initializeTrackSizes(grid, dir, trackSizes, availableSpace)
  <span class="hljs-comment"># 2. Resolve Intrinsic Track Sizes</span>
  resolveIntrinsicTrackSizes(grid, dir, trackSizes, availableSpace)
  <span class="hljs-comment"># 3. Maximize Tracks</span>
  maximizeTracks(grid, dir, availableSpace)
  <span class="hljs-comment"># 4. Expand Flexible Tracks</span>
  expandFlexibleTracks(grid, dir, availableSpace)
  <span class="hljs-comment"># 5. Expand Stretched Auto Tracks</span>
  expandStretchedAutoTracks(grid, dir, availableSpace)
  <span class="hljs-comment"># Now compute final positions</span>
  computeTrackPositions(grid, dir)
</code></pre>
<ol start="2">
<li><strong>Proper Auto Placement</strong>: The implementation now correctly handles auto-flow grid items, respecting the grid's flow direction and density:</li>
</ol>
<pre><code class="lang-ruby"><span class="hljs-comment"># Auto-flow implementation that correctly places items based on direction</span>
<span class="hljs-keyword">if</span> gridTemplate.autoFlow <span class="hljs-keyword">in</span> [grRow, grRowDense]:
  (mx, my) = (dcol, drow)
elif gridTemplate.autoFlow <span class="hljs-keyword">in</span> [grColumn, grColumnDense]:
  (mx, my) = (drow, dcol)
</code></pre>
<ol start="3">
<li><strong>Calculating minimum and maximum function constraints</strong>: proper handling of minimum and maximum “functions” where previously I’d tried to calculate them ad-hoc without tracking the minimum or maximum space of each track.</li>
</ol>
<h2 id="heading-visual-verification">Visual Verification</h2>
<p>To validate the implementation, I created test cases like this one that verifies precise grid line positions:</p>
<pre><code class="lang-ruby">test <span class="hljs-string">"compute others"</span>:
  var <span class="hljs-symbol">gt:</span> GridTemplate

  parseGridTemplateColumns gt, [<span class="hljs-string">"first"</span>] <span class="hljs-number">40</span><span class="hljs-string">'ux \
    ["second", "line2"] 50'</span>ux \
    [<span class="hljs-string">"line3"</span>] auto \
    [<span class="hljs-string">"col4-start"</span>] <span class="hljs-number">50</span><span class="hljs-string">'ux \
    ["five"] 40'</span>ux [<span class="hljs-string">"end"</span>]
  parseGridTemplateRows gt, [<span class="hljs-string">"row1-start"</span>] <span class="hljs-number">25</span><span class="hljs-string">'pp \
    ["row1-end"] 100'</span>ux \
    [<span class="hljs-string">"third-line"</span>] auto [<span class="hljs-string">"last-line"</span>]

  gt.gaps[dcol] = <span class="hljs-number">10</span>.UiScalar
  gt.gaps[drow] = <span class="hljs-number">10</span>.UiScalar
  var <span class="hljs-symbol">computedSizes:</span> array[GridDir, Table[int, ComputedTrackSize]]
  gt.computeTracks(uiBox(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">1000</span>), computedSizes)

  <span class="hljs-comment"># Verify precise grid line positions</span>
  check gt.lines[dcol][<span class="hljs-number">0</span>].start.float == <span class="hljs-number">0</span>.<span class="hljs-number">0</span>
  check gt.lines[dcol][<span class="hljs-number">1</span>].start.float == <span class="hljs-number">50.0</span>
  check gt.lines[dcol][<span class="hljs-number">2</span>].start.float == <span class="hljs-number">110.0</span>  <span class="hljs-comment"># 40 + 10(gap) + 50 + 10(gap)</span>
  check gt.lines[dcol][<span class="hljs-number">3</span>].start.float == <span class="hljs-number">900.0</span>  <span class="hljs-comment"># auto fills remaining space</span>
  check gt.lines[dcol][<span class="hljs-number">4</span>].start.float == <span class="hljs-number">960.0</span>
  check gt.lines[dcol][<span class="hljs-number">5</span>].start.float == <span class="hljs-number">1000.0</span>
</code></pre>
<p>I've even created an HTML visualization to help me understand how these values map to a real grid:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743384009407/e4c4d977-4b4a-4a92-a8a3-bfa5924e006b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-insights-and-takeaways">Insights and Takeaways</h2>
<p>What I found most fascinating was how the LLM benefited from the same debugging approach I use myself. Without the right debug logs, both the model and I struggled to locate exactly where the code was failing.</p>
<p>The revised implementation is much more standards-compliant and handles complex cases that my original implementation struggled with:</p>
<ol>
<li><p><strong>Content-based sizing</strong>: Proper handling of min-content, max-content, and fit-content</p>
</li>
<li><p><strong>Nested and spanning items</strong>: Correct distribution of space for items that span multiple tracks</p>
</li>
<li><p><strong>Auto placement</strong>: Better handling of grid auto-flow direction and dense packing</p>
</li>
</ol>
<p>Like much systems programming, CSS grid layout is deceptively complex beneath its elegant API. Implementing the spec faithfully required understanding subtle interactions between sizing algorithms, content contributions, and space distribution. Honestly I don’t care about that so much. I just want to make apps. ;)</p>
]]></content:encoded></item><item><title><![CDATA[Making Reactive Datatypes in 200 lines of Nim]]></title><description><![CDATA[Sigil becomes more than a name
Sigils now supports first-class reactive data types! I wish I could say it was a grueling undertaking, but it wasn’t. Thanks to the core of Sigil’s being signals and slots along with Nim’s superb meta-programming it was...]]></description><link>https://blog.elcritch.net/making-sigils-reactive</link><guid isPermaLink="true">https://blog.elcritch.net/making-sigils-reactive</guid><category><![CDATA[Figuro]]></category><category><![CDATA[Reactive Programming]]></category><category><![CDATA[Nim]]></category><category><![CDATA[signals]]></category><category><![CDATA[Angular]]></category><category><![CDATA[GUI]]></category><category><![CDATA[UI]]></category><category><![CDATA[native]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Thu, 30 Jan 2025 04:48:24 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-sigil-becomes-more-than-a-name"><code>Sigil</code> becomes more than a name</h2>
<p><a target="_blank" href="https://github.com/elcritch/sigils">Sigils</a> now supports first-class reactive data types! I wish I could say it was a grueling undertaking, but it wasn’t. Thanks to the core of Sigil’s being signals and slots along with Nim’s superb meta-programming it was pretty trivial to knock out. The trickiest part has been finding out what behaviors the JavaScript implementations follow.</p>
<p>Coincidently, <a target="_blank" href="https://www.infoworld.com/article/2336000/reactive-magic-in-svelte-5-understanding-runes.html">Svelte’s</a> reactive datatype is called <code>Rune</code> meaning <em>”a mark or letter of mysterious or magic significance”</em> while <code>Sigil</code> means <em>“an inscribed or painted symbol considered to have magical power“</em>. Almost like this is what the Sigils library was meant to become.</p>
<p>I’m excited since this <em>neatly</em> solves a longstanding wart in <a target="_blank" href="https://github.com/elcritch/figuro">Figuro</a> UIs for how to provide a nice clean and simple API for handling reactive data. Signals and slots are powerful but a bit verbose and my initial APIs left me feeling underwhelmed. Reactive datatypes are much nicer:</p>
<pre><code class="lang-ocaml">    <span class="hljs-keyword">let</span> x = newSigil(<span class="hljs-number">5</span>)
    <span class="hljs-keyword">let</span> y = newSigil(<span class="hljs-literal">false</span>)
    <span class="hljs-keyword">let</span> z = computed[<span class="hljs-built_in">int</span>]<span class="hljs-literal">()</span>:
      <span class="hljs-keyword">if</span> y{}: x{} * <span class="hljs-number">2</span> <span class="hljs-keyword">else</span>: <span class="hljs-number">0</span>

    check x{} == <span class="hljs-number">5</span>
    check y{} == <span class="hljs-literal">false</span>
    check z{} == <span class="hljs-number">0</span>
    y &lt;- <span class="hljs-literal">true</span> #<span class="hljs-comment">(* update the calue in y *)</span>
    check y{} == <span class="hljs-literal">true</span>
    check z{} == <span class="hljs-number">10</span>
</code></pre>
<p>Previously in Figuro I was trying to do something similar but it just wasn’t <em>it</em>:</p>
<pre><code class="lang-ruby">type Main* = ref object of Figuro
       counter = Property[int]()

proc draw*(<span class="hljs-symbol">self:</span> Main) {.slot.} =
   withWidget(<span class="hljs-keyword">self</span>):
      rectangle <span class="hljs-string">"count"</span>:
        text <span class="hljs-string">"btnText"</span>:
          bindProp(<span class="hljs-keyword">self</span>.counter)
          node.setText({<span class="hljs-symbol">font:</span> $(<span class="hljs-keyword">self</span>.counter.value) &amp; <span class="hljs-string">" ₿"</span> }, Center, Middle)    
      Button.new <span class="hljs-string">"btnAdd"</span>:
        text <span class="hljs-string">"btnText"</span>:
          node.setText({<span class="hljs-symbol">largeFont:</span> <span class="hljs-string">"–"</span>}, Center, Middle)
        <span class="hljs-comment">## something like this:</span>
        <span class="hljs-keyword">self</span>.counter.onSignal(doClicked) <span class="hljs-keyword">do</span>(<span class="hljs-symbol">counter:</span> Property[int]):
          counter.update(counter.value-<span class="hljs-number">1</span>)
</code></pre>
<p>This can now become more streamlined and provide a semantics familiar to webdevs (note I haven’t wired this bit into Figuro at publication time):</p>
<pre><code class="lang-ruby">type Main* = ref object of Figuro
       counter = newSigil[int](<span class="hljs-number">0</span>)

proc draw*(<span class="hljs-symbol">self:</span> Main) {.slot.} =
   withWidget(<span class="hljs-keyword">self</span>):
      rectangle <span class="hljs-string">"count"</span>:
        text <span class="hljs-string">"btnText"</span>:
          node.setText({<span class="hljs-symbol">font:</span> $(<span class="hljs-keyword">self</span>.counter{}) &amp; <span class="hljs-string">" ₿"</span> }, Center, Middle)    
      Button.new <span class="hljs-string">"btnAdd"</span>:
        text <span class="hljs-string">"btnText"</span>:
          node.setText({<span class="hljs-symbol">largeFont:</span> <span class="hljs-string">"–"</span>}, Center, Middle)
        node.onSignal(doClicked):
          <span class="hljs-keyword">self</span>.counter &lt;- <span class="hljs-keyword">self</span>.counter{} - <span class="hljs-number">1</span>
</code></pre>
<h2 id="heading-sigilt-villains-origin-story"><code>Sigil[T]</code> Villain’s Origin Story</h2>
<p>The other day I posted on Nim-lang’s <code>#appdev</code> Discord channel about adding some animation support to <a target="_blank" href="https://github.com/elcritch/figuro">Figuro</a>. Using the new <em>signals and slots</em> from my new <a target="_blank" href="https://github.com/elcritch/sigils">Sigils</a> made creating a new more powerful <a target="_blank" href="https://github.com/elcritch/figuro/blob/e09cd458edb571fbd830f4d0b7854d942ab59854/src/figuro/ui/animations.nim#L7"><code>Fader</code></a> primitive super easy.</p>
<p>The <code>Fader</code> Agent (Sigil’s core object) fades in or out on a linear scale and can be interrupted midway and start going the other direction. In a couple dozen lines it seamlessly computes the next step based on Figuro’s <code>doTick</code> core signal when it begins fading and disconnects itself when it’s finished. <code>Fader</code> provides a core primitive needed for many key UI operations include fading a button on hover or sliding a menu in or out.</p>
<p>Phil, a fellow participant in <code>#appdev</code> (aka IsoFruit), asked about reactive datatypes. Phil does a lot of webdev and apparently *signals* have taken over the JavaScript UI world. Given that I had just made <code>Fader</code> which is a very reactive objects, I was curious what he meant and we started discussing it. He mentioned reactive data types were becoming standard in JavaScript with them being called Observables in RxJs, Signals in Angular, and Runes in Svelte.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738209771616/1d7a926a-8849-4532-8650-23c58e109e00.png" alt class="image--center mx-auto" /></p>
<p>After some back and forth it turned out he was referring to <a target="_blank" href="https://en.wikipedia.org/wiki/Reactive_programming">Reactive Programming</a>. It’s an interesting field and I wasn’t sure how it’d fit into a static language like Nim. I’d experimented with it briefly way back in 2014 (almost a decade now :wow:) to run a PID loop on a Raspberry Pi. That experiment didn’t turn out well and was one of my first forays into latency sensitive programming and dealing with the pain of GC.</p>
<p>Nowadays <em>Reactive Programming</em> has moved into the mainstream and the method <em>de jour</em> of handling reactive user interfaces. That’s basically <em>all</em> UIs. Here’s a short example given by Phil:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> x = signal(<span class="hljs-number">5</span>)
<span class="hljs-keyword">const</span> double = computed(<span class="hljs-function">() =&gt;</span> x()*<span class="hljs-number">2</span>)
x.set(<span class="hljs-number">2</span>)
<span class="hljs-comment">// double is now 4</span>
</code></pre>
<p>Well I figured that looks simple. I bet Figuro (really <em>Sigils</em>) could make short work of that. A short 20 minutes later I had a working example! Sorta, well behold:</p>
<pre><code class="lang-ocaml">    <span class="hljs-keyword">type</span>
      <span class="hljs-type">Reactive</span> = <span class="hljs-built_in">ref</span> <span class="hljs-keyword">object</span> <span class="hljs-keyword">of</span> <span class="hljs-type">Agent</span>
        <span class="hljs-keyword">value</span>: <span class="hljs-built_in">int</span>
    proc setValue(tp: <span class="hljs-type">Reactive</span>, <span class="hljs-keyword">val</span>: <span class="hljs-built_in">int</span>) {.signal.}
    <span class="hljs-keyword">let</span>
      x = <span class="hljs-type">Reactive</span>(<span class="hljs-keyword">value</span>: <span class="hljs-number">5</span>)
      y = <span class="hljs-type">Reactive</span><span class="hljs-literal">()</span>

    proc computed(self: <span class="hljs-type">Reactive</span>, <span class="hljs-keyword">val</span>: <span class="hljs-built_in">int</span>) {.slot.} =
      self.<span class="hljs-keyword">value</span> = <span class="hljs-keyword">val</span> * <span class="hljs-number">2</span>
    x.connect(setValue, y, computed)

    emit x.setValue(<span class="hljs-number">2</span>)
    echo <span class="hljs-string">"Y: "</span>, y.<span class="hljs-keyword">value</span>
</code></pre>
<p>This was lacking some syntactic sugar, but proved to me that it could be done. The next day I added a couple of <code>template</code>'s and got:</p>
<pre><code class="lang-ocaml">  test <span class="hljs-string">"reactive wrapper"</span>:
    <span class="hljs-keyword">let</span>
      x = reactive(<span class="hljs-number">5</span>)
      y = computed(x): # probably need a macro <span class="hljs-keyword">to</span> get rid <span class="hljs-keyword">of</span> the extra x
        <span class="hljs-number">2</span> * x{}

    x &lt;- <span class="hljs-number">2</span>
    echo <span class="hljs-string">"X: "</span>, x.<span class="hljs-keyword">value</span>
    echo <span class="hljs-string">"Y: "</span>, y.<span class="hljs-keyword">value</span>
    check y.<span class="hljs-keyword">value</span> == <span class="hljs-number">4</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Complex C code gave ChatGPT a headache and Claude came to the rescue]]></title><description><![CDATA[For Figuro I want to have a file monitoring API that watches for the CSS theme to be modified. Unfortunately all the libraries in Nim are either wrappers around C libraries or incomplete.
I tried finding a few C projects and translating them, but it’...]]></description><link>https://blog.elcritch.net/complex-c-code-gave-chatgpt-a-headache-and-claude-came-to-the-rescue</link><guid isPermaLink="true">https://blog.elcritch.net/complex-c-code-gave-chatgpt-a-headache-and-claude-came-to-the-rescue</guid><category><![CDATA[code-translation]]></category><category><![CDATA[porting]]></category><category><![CDATA[llm]]></category><category><![CDATA[Nim]]></category><category><![CDATA[coding]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Tue, 14 Jan 2025 23:37:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/AZrBFoXP_3I/upload/8d653571fe6c0773f6ff1a18913ff4ee.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For Figuro I want to have a file monitoring API that watches for the CSS theme to be modified. Unfortunately all the libraries in Nim are either wrappers around C libraries or incomplete.</p>
<p>I tried finding a few C projects and translating them, but it’s a pain because it requires using low level system APIs which also need binding in Nim.</p>
<h2 id="heading-notes-about-translating">Notes About Translating</h2>
<p>I also tried converting the code automatically using <code>c2nim</code> which is very helpful. Unfortunately for this sort of system code you end up with lots of missing details.</p>
<p>For example on MacOS file watching libraries tend to use <code>FSEventStreamContext</code> which I’ve never even heard about before. It requires plumbing some handlers for allocating and deallocating things event structs. Or something as I don’t really care about the details. Others figured that out, I just want to use it.</p>
<p>Most of converting C to Nim (or any programming language to another) is just going through a fairly simple set of mini-algorithms and mappings. For example C enum flags can be converted into Nim’s <code>set[SomeEnum]</code> which is much easier to work with and typesafe. It also casts back to the proper C integer values!</p>
<p>Now programming this knowledge into something like <code>c2nim</code> takes a lot of work. I know this well since I’ve <a target="_blank" href="https://github.com/nim-lang/c2nim/pulls?q=is%3Apr+is%3Aclosed+author%3Aelcritch+">contributed a fair bit to c2nim in the past</a>. It’s fun but becomes pretty tedious since there are dozens if not hundreds of these mini-algorithms and C isn’t very strict almost to the point that it’s really considered a typed language. It’s more <em>kinda-typed</em>, definitely unlike Haskell’s <a target="_blank" href="https://serokell.io/blog/kinds-and-hkts-in-haskell">higher kinded types</a>.</p>
<p>Isn’t this what LLMs should be great at however? They really boil down to very complex [Markov chains](<a target="_blank" href="https://en.wikipedia.org/wiki/Markov_chain">https://en.wikipedia.org/wiki/Markov_chain</a>) compiled over the entire internet with some fascinating emergent behavior. It almost seems like they can <em>reason</em> about a problem. Really though it’s more like they learn complex flow diagrams and decision trees from all the hard work us humans have put into <em>everything</em> the past umpteen millennium. That’s also why I think <a target="_blank" href="https://basicincome.stanford.edu/about/what-is-ubi/">UBI</a> makes sense as automation builds upon the work of generations of minds!</p>
<p>Okay back to the core topic of translating code.</p>
<h2 id="heading-trying-out-chatgpt">Trying out ChatGPT</h2>
<p>Eventually I figured I’d give ChatGPT a go. It started out great! But then it essentially gave up:</p>
<pre><code class="lang-ocaml"># <span class="hljs-type">Enums</span> translated <span class="hljs-keyword">to</span> <span class="hljs-type">Nim</span>
<span class="hljs-keyword">type</span>
  <span class="hljs-type">DmonWatchFlags</span>* = enum
    recursive = <span class="hljs-number">0x1</span>,
    followSymlinks = <span class="hljs-number">0x2</span>,
    outOfScopeLinks = <span class="hljs-number">0x4</span>,
    ignoreDirectories = <span class="hljs-number">0x8</span>

  <span class="hljs-type">DmonAction</span>* = enum
    create = <span class="hljs-number">1</span>,
    delete,
    modify,
    move

... more structs

# <span class="hljs-type">Utility</span> functions
proc isRange*(ch: <span class="hljs-built_in">char</span>, from: <span class="hljs-built_in">char</span>, <span class="hljs-keyword">to</span>: <span class="hljs-built_in">char</span>): <span class="hljs-built_in">bool</span> =
  result = uint8(ch - from) &lt;= uint8(<span class="hljs-keyword">to</span> - from)

proc isUpperChar*(ch: <span class="hljs-built_in">char</span>): <span class="hljs-built_in">bool</span> =
  result = isRange(ch, <span class="hljs-string">'A'</span>, <span class="hljs-string">'Z'</span>)

proc toLowerChar*(ch: <span class="hljs-built_in">char</span>): <span class="hljs-built_in">char</span> =
  <span class="hljs-keyword">if</span> isUpperChar(ch):
    result = ch + <span class="hljs-string">'\x20'</span>
  <span class="hljs-keyword">else</span>:
    result = ch

proc toLower*(dst: var <span class="hljs-built_in">string</span>, str: <span class="hljs-built_in">string</span>) =
  <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> str:
    dst.add(toLowerChar(c))

# <span class="hljs-type">Initialization</span> <span class="hljs-keyword">function</span>
proc dmonInit*<span class="hljs-literal">()</span> =
  echo <span class="hljs-string">"Initializing dmon..."</span>

proc dmonDeinit*<span class="hljs-literal">()</span> =
  echo <span class="hljs-string">"Deinitializing dmon..."</span>
</code></pre>
<p>The beginning looks like it’s off to a great start. However both <code>dmonInit</code> and <code>dmonDeinit</code> are filler procs. So I tried prompting ChatGPT for more and it kept doing the same habit of trying to skimp out on doing a full translation.</p>
<p>Also, ChatGPT noticeably began to struggle outputting the code when you included fuller details. It’s unclear if that’s due to throttling by OpenAI or that the model requires more resources.</p>
<h2 id="heading-on-to-claude">On to Claude</h2>
<p>Next I figured I’d try Claude Sonnet 3.5 from Anthropic. Word on the street is that it’s much better at coding.</p>
<p>It’s first attempts was similar to ChatGPT in that it gave a summary of <em>how</em> to do the conversion. Well great, it wants to put <em>me</em> to work for <em>it</em>. I’m starting to feel like LLMs have great management potential! :P</p>
<p>Okay, but once I prompted Claude to give me the full full translation it obliged! Well at least until:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736897323671/ec450b42-1a90-4b07-9180-76bdf66d8f64.png" alt class="image--center mx-auto" /></p>
<p>But a quick follow up prompt got it to finish:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736897484468/29c6a5ad-953b-48e9-b083-239e95e30686.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-results">Results</h2>
<p>Below is the full output for the MacOS specific conversion for those curious about such things. Does it compile? Nope! But that looks like a great start. I’ve been skimming it and comparing it to the C code and it’s done a good job converting.</p>
<p>I tend to trust LLMs for <em>translating</em> the code since they do better against something that exists and this sort of common system code which has a lot of similar implementation in many languages. It’s not creating <em>de novo</em> code.</p>
<pre><code class="lang-ocaml">
import posix
import macros
import os
import strutils
import locks
import tables

# <span class="hljs-type">MacOS</span>-specific imports
{.passL: <span class="hljs-string">"-framework CoreServices -framework CoreFoundation"</span>.}

<span class="hljs-keyword">type</span>
  <span class="hljs-type">CFIndex</span>* = clong
  <span class="hljs-type">CFTimeInterval</span>* = cdouble
  <span class="hljs-type">CFStringRef</span>* = distinct pointer
  <span class="hljs-type">CFArrayRef</span>* = distinct pointer
  <span class="hljs-type">CFRunLoopRef</span>* = distinct pointer
  <span class="hljs-type">CFAllocatorRef</span>* = distinct pointer
  <span class="hljs-type">CFRunLoopMode</span>* = distinct pointer
  <span class="hljs-type">FSEventStreamRef</span>* = distinct pointer
  <span class="hljs-type">FSEventStreamEventId</span>* = culonglong
  <span class="hljs-type">FSEventStreamEventFlags</span>* = culong
  dispatch_semaphore_t* = distinct pointer

const
  kCFStringEncodingUTF8* = <span class="hljs-number">0x08000100</span><span class="hljs-symbol">'i32</span>
  kCFRunLoopDefaultMode* = <span class="hljs-type">CFRunLoopMode</span>(<span class="hljs-type">CFStringRef</span>(<span class="hljs-string">"kCFRunLoopDefaultMode"</span>))
  kCFRunLoopRunTimedOut* = <span class="hljs-number">2</span>
  kFSEventStreamEventFlagItemCreated* = <span class="hljs-number">0x00000100</span><span class="hljs-symbol">'u32</span>
  kFSEventStreamEventFlagItemRemoved* = <span class="hljs-number">0x00000200</span><span class="hljs-symbol">'u32</span>
  kFSEventStreamEventFlagItemModified* = <span class="hljs-number">0x00001000</span><span class="hljs-symbol">'u32</span>
  kFSEventStreamEventFlagItemRenamed* = <span class="hljs-number">0x00000800</span><span class="hljs-symbol">'u32</span>
  kFSEventStreamCreateFlagFileEvents* = <span class="hljs-number">0x00000010</span><span class="hljs-symbol">'u32</span>

<span class="hljs-keyword">type</span>
  <span class="hljs-type">DmonWatchId</span>* = distinct uint32

  <span class="hljs-type">DmonWatchFlags</span>* = enum
    <span class="hljs-type">Recursive</span> = <span class="hljs-number">0x1</span>
    <span class="hljs-type">FollowSymlinks</span> = <span class="hljs-number">0x2</span> 
    <span class="hljs-type">OutOfScopeLinks</span> = <span class="hljs-number">0x4</span>
    <span class="hljs-type">IgnoreDirectories</span> = <span class="hljs-number">0x8</span>

  <span class="hljs-type">DmonAction</span>* = enum
    <span class="hljs-type">Create</span> = <span class="hljs-number">1</span>
    <span class="hljs-type">Delete</span>
    <span class="hljs-type">Modify</span> 
    <span class="hljs-type">Move</span>

  <span class="hljs-type">DmonWatchCallback</span>* = proc(watchId: <span class="hljs-type">DmonWatchId</span>, action: <span class="hljs-type">DmonAction</span>, 
                          rootdir, filepath, oldfilepath: <span class="hljs-built_in">string</span>,
                          userData: pointer) {.cdecl.}

  <span class="hljs-type">FSEventStreamContext</span>* {.pure, final.} = <span class="hljs-keyword">object</span>
    version*: <span class="hljs-type">CFIndex</span>
    info*: pointer
    retain*: pointer
    release*: pointer
    copyDescription*: pointer

  <span class="hljs-type">DmonFsEvent</span> = <span class="hljs-keyword">object</span>
    filepath: <span class="hljs-built_in">string</span>
    eventId: <span class="hljs-type">FSEventStreamEventId</span>
    eventFlags: <span class="hljs-type">FSEventStreamEventFlags</span>
    watchId: <span class="hljs-type">DmonWatchId</span>
    skip: <span class="hljs-built_in">bool</span>
    moveValid: <span class="hljs-built_in">bool</span>

  <span class="hljs-type">DmonWatchState</span> = <span class="hljs-built_in">ref</span> <span class="hljs-keyword">object</span>
    id: <span class="hljs-type">DmonWatchId</span>
    watchFlags: uint32
    fsEvStreamRef: <span class="hljs-type">FSEventStreamRef</span>
    watchCb: <span class="hljs-type">DmonWatchCallback</span>
    userData: pointer
    rootdir: <span class="hljs-built_in">string</span>
    rootdirUnmod: <span class="hljs-built_in">string</span>
    init: <span class="hljs-built_in">bool</span>

  <span class="hljs-type">DmonState</span> = <span class="hljs-keyword">object</span>
    watches: <span class="hljs-built_in">array</span>[<span class="hljs-number">64</span>, <span class="hljs-type">DmonWatchState</span>]
    freeList: <span class="hljs-built_in">array</span>[<span class="hljs-number">64</span>, <span class="hljs-built_in">int</span>]
    events: seq[<span class="hljs-type">DmonFsEvent</span>]
    numWatches: <span class="hljs-built_in">int</span>
    modifyWatches: <span class="hljs-type">Atomic</span>[<span class="hljs-built_in">int</span>]
    threadHandle: <span class="hljs-type">Thread</span>[void]
    threadLock: <span class="hljs-type">Lock</span>
    threadSem: dispatch_semaphore_t
    cfLoopRef: <span class="hljs-type">CFRunLoopRef</span>
    cfAllocRef: <span class="hljs-type">CFAllocatorRef</span>
    quit: <span class="hljs-built_in">bool</span>

# <span class="hljs-type">CoreFoundation</span> <span class="hljs-type">Functions</span>
proc <span class="hljs-type">CFStringCreateWithCString</span>*(alloc: <span class="hljs-type">CFAllocatorRef</span>, cStr: cstring, encoding: <span class="hljs-built_in">int32</span>): <span class="hljs-type">CFStringRef</span> {.importc.}
proc <span class="hljs-type">CFArrayCreate</span>*(alloc: <span class="hljs-type">CFAllocatorRef</span>, values: ptr pointer, numValues: <span class="hljs-type">CFIndex</span>, callbacks: pointer): <span class="hljs-type">CFArrayRef</span> {.importc.}
proc <span class="hljs-type">CFRunLoopGetCurrent</span>*<span class="hljs-literal">()</span>: <span class="hljs-type">CFRunLoopRef</span> {.importc.}
proc <span class="hljs-type">CFRunLoopRunInMode</span>*(mode: <span class="hljs-type">CFRunLoopMode</span>, seconds: <span class="hljs-type">CFTimeInterval</span>, returnAfterSourceHandled: <span class="hljs-built_in">bool</span>): cint {.importc.}
proc <span class="hljs-type">CFRunLoopStop</span>*(loop: <span class="hljs-type">CFRunLoopRef</span>) {.importc.}
proc <span class="hljs-type">CFRelease</span>*(cf: pointer) {.importc.}

# <span class="hljs-type">FSEvents</span> <span class="hljs-type">Functions</span>
proc <span class="hljs-type">FSEventStreamCreate</span>*(
  allocator: <span class="hljs-type">CFAllocatorRef</span>, 
  callback: proc (
    streamRef: <span class="hljs-type">FSEventStreamRef</span>,
    clientCallBackInfo: pointer,
    numEvents: csize_t,
    eventPaths: pointer,
    eventFlags: ptr <span class="hljs-type">FSEventStreamEventFlags</span>,
    eventIds: ptr <span class="hljs-type">FSEventStreamEventId</span>
  ) {.cdecl.},
  context: ptr <span class="hljs-type">FSEventStreamContext</span>,
  pathsToWatch: <span class="hljs-type">CFArrayRef</span>,
  sinceWhen: <span class="hljs-type">FSEventStreamEventId</span>,
  latency: <span class="hljs-type">CFTimeInterval</span>,
  flags: <span class="hljs-type">FSEventStreamEventFlags</span>
): <span class="hljs-type">FSEventStreamRef</span> {.importc.}

proc <span class="hljs-type">FSEventStreamScheduleWithRunLoop</span>*(
  streamRef: <span class="hljs-type">FSEventStreamRef</span>,
  runLoop: <span class="hljs-type">CFRunLoopRef</span>,
  runLoopMode: <span class="hljs-type">CFRunLoopMode</span>
) {.importc.}

proc <span class="hljs-type">FSEventStreamStart</span>*(streamRef: <span class="hljs-type">FSEventStreamRef</span>): <span class="hljs-built_in">bool</span> {.importc.}
proc <span class="hljs-type">FSEventStreamStop</span>*(streamRef: <span class="hljs-type">FSEventStreamRef</span>) {.importc.}
proc <span class="hljs-type">FSEventStreamInvalidate</span>*(streamRef: <span class="hljs-type">FSEventStreamRef</span>) {.importc.}
proc <span class="hljs-type">FSEventStreamRelease</span>*(streamRef: <span class="hljs-type">FSEventStreamRef</span>) {.importc.}

# <span class="hljs-type">Grand</span> <span class="hljs-type">Central</span> <span class="hljs-type">Dispatch</span> <span class="hljs-type">Functions</span>
proc dispatch_semaphore_create*(<span class="hljs-keyword">value</span>: clong): dispatch_semaphore_t {.importc.}
proc dispatch_semaphore_signal*(sem: dispatch_semaphore_t): clong {.importc.}
proc dispatch_semaphore_wait*(sem: dispatch_semaphore_t, timeout: uint64): clong {.importc.}
proc dispatch_release*(obj: pointer) {.importc.}

const <span class="hljs-type">DISPATCH_TIME_FOREVER</span>* = not <span class="hljs-number">0</span><span class="hljs-symbol">'u64</span>

var
  dmonInitialized: <span class="hljs-built_in">bool</span>
  dmon: <span class="hljs-type">DmonState</span>

proc fsEventCallback(
  streamRef: <span class="hljs-type">FSEventStreamRef</span>,
  userData: pointer,
  numEvents: csize_t,
  eventPaths: pointer,
  eventFlags: ptr <span class="hljs-type">FSEventStreamEventFlags</span>,
  eventIds: ptr <span class="hljs-type">FSEventStreamEventId</span>) {.cdecl.} =

  <span class="hljs-keyword">let</span> watchId = cast[<span class="hljs-type">DmonWatchId</span>](userData)
  <span class="hljs-keyword">assert</span>(uint32(watchId) &gt; <span class="hljs-number">0</span>)

  <span class="hljs-keyword">let</span> watch = dmon.watches[uint32(watchId) - <span class="hljs-number">1</span>]
  <span class="hljs-keyword">let</span> paths = cast[ptr <span class="hljs-type">UncheckedArray</span>[cstring]](eventPaths)

  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;numEvents:
    var ev = <span class="hljs-type">DmonFsEvent</span><span class="hljs-literal">()</span>
    <span class="hljs-keyword">let</span> path = $paths[i]

    # <span class="hljs-type">Convert</span> path <span class="hljs-keyword">to</span> unix style <span class="hljs-keyword">and</span> make relative <span class="hljs-keyword">to</span> watch root
    var absPath = unixPath(path)
    <span class="hljs-keyword">let</span> watchRoot = watch.rootdir.toLowerAscii

    <span class="hljs-keyword">if</span> not absPath.startsWith(watchRoot):
      continue

    ev.filepath = absPath[watchRoot.len..^<span class="hljs-number">1</span>]
    ev.eventFlags = eventFlags[i]
    ev.eventId = eventIds[i]
    ev.watchId = watchId

    dmon.events.add(ev)

proc processEvents<span class="hljs-literal">()</span> =
  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;dmon.events.len:
    var ev = addr dmon.events[i]
    <span class="hljs-keyword">if</span> ev.skip:
      continue

    # <span class="hljs-type">Coalesce</span> multiple modify events
    <span class="hljs-keyword">if</span> (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemModified) != <span class="hljs-number">0</span>:
      <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> (i+<span class="hljs-number">1</span>)..&lt;dmon.events.len:
        <span class="hljs-keyword">let</span> checkEv = addr dmon.events[j]
        <span class="hljs-keyword">if</span> (checkEv.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemModified) != <span class="hljs-number">0</span> <span class="hljs-keyword">and</span>
           checkEv.filepath == ev.filepath:
          ev.skip = <span class="hljs-literal">true</span>
          break

    # <span class="hljs-type">Handle</span> renames
    elif (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemRenamed) != <span class="hljs-number">0</span> <span class="hljs-keyword">and</span>
         not ev.moveValid:
      <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> (i+<span class="hljs-number">1</span>)..&lt;dmon.events.len:
        <span class="hljs-keyword">let</span> checkEv = addr dmon.events[j]
        <span class="hljs-keyword">if</span> (checkEv.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemRenamed) != <span class="hljs-number">0</span> <span class="hljs-keyword">and</span>
           checkEv.eventId == ev.eventId + <span class="hljs-number">1</span>:
          ev.moveValid = <span class="hljs-literal">true</span>
          checkEv.moveValid = <span class="hljs-literal">true</span>
          break

      # <span class="hljs-type">If</span> no matching rename found, treat <span class="hljs-keyword">as</span> create/delete
      <span class="hljs-keyword">if</span> not ev.moveValid:
        ev.eventFlags = ev.eventFlags <span class="hljs-keyword">and</span> not kFSEventStreamEventFlagItemRenamed
        <span class="hljs-keyword">let</span> absPath = watch.rootdir / ev.filepath

        <span class="hljs-keyword">if</span> not fileExists(absPath):
          ev.eventFlags = ev.eventFlags <span class="hljs-keyword">or</span> kFSEventStreamEventFlagItemRemoved
        <span class="hljs-keyword">else</span>:
          ev.eventFlags = ev.eventFlags <span class="hljs-keyword">or</span> kFSEventStreamEventFlagItemCreated

  # <span class="hljs-type">Process</span> final events
  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;dmon.events.len:
    <span class="hljs-keyword">let</span> ev = addr dmon.events[i]
    <span class="hljs-keyword">if</span> ev.skip:
      continue

    <span class="hljs-keyword">let</span> watch = dmon.watches[uint32(ev.watchId) - <span class="hljs-number">1</span>]
    <span class="hljs-keyword">if</span> watch == nil <span class="hljs-keyword">or</span> watch.watchCb == nil:
      continue

    <span class="hljs-keyword">if</span> (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemCreated) != <span class="hljs-number">0</span>:
      watch.watchCb(ev.watchId, <span class="hljs-type">Create</span>, watch.rootdirUnmod, ev.filepath, nil, watch.userData)

    <span class="hljs-keyword">if</span> (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemModified) != <span class="hljs-number">0</span>:
      watch.watchCb(ev.watchId, <span class="hljs-type">Modify</span>, watch.rootdirUnmod, ev.filepath, nil, watch.userData)

    elif (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemRenamed) != <span class="hljs-number">0</span>:
      <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> (i+<span class="hljs-number">1</span>)..&lt;dmon.events.len:
        <span class="hljs-keyword">let</span> checkEv = addr dmon.events[j]
        <span class="hljs-keyword">if</span> (checkEv.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemRenamed) != <span class="hljs-number">0</span>:
          watch.watchCb(checkEv.watchId, <span class="hljs-type">Move</span>, watch.rootdirUnmod,
                       checkEv.filepath, ev.filepath, watch.userData)
          break

    elif (ev.eventFlags <span class="hljs-keyword">and</span> kFSEventStreamEventFlagItemRemoved) != <span class="hljs-number">0</span>:
      watch.watchCb(ev.watchId, <span class="hljs-type">Delete</span>, watch.rootdirUnmod, ev.filepath, nil, watch.userData)

  dmon.events.setLen(<span class="hljs-number">0</span>)

proc monitorThread<span class="hljs-literal">()</span> {.thread.} =
  dmon.cfLoopRef = <span class="hljs-type">CFRunLoopGetCurrent</span><span class="hljs-literal">()</span>
  dispatch_semaphore_signal(dmon.threadSem)

  <span class="hljs-keyword">while</span> not dmon.quit:
    <span class="hljs-keyword">if</span> dmon.modifyWatches.load<span class="hljs-literal">()</span> != <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> not tryLock(dmon.threadLock):
      sleep(<span class="hljs-number">10</span>)
      continue

    <span class="hljs-keyword">if</span> dmon.numWatches == <span class="hljs-number">0</span>:
      sleep(<span class="hljs-number">10</span>)
      unlock(dmon.threadLock)
      continue

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;dmon.numWatches:
      <span class="hljs-keyword">let</span> watch = dmon.watches[i]
      <span class="hljs-keyword">if</span> not watch.init:
        <span class="hljs-keyword">assert</span>(not watch.fsEvStreamRef.isNil)
        <span class="hljs-type">FSEventStreamScheduleWithRunLoop</span>(watch.fsEvStreamRef, dmon.cfLoopRef, kCFRunLoopDefaultMode)
        discard <span class="hljs-type">FSEventStreamStart</span>(watch.fsEvStreamRef)
        watch.init = <span class="hljs-literal">true</span>

    discard <span class="hljs-type">CFRunLoopRunInMode</span>(kCFRunLoopDefaultMode, <span class="hljs-number">0.5</span>, <span class="hljs-literal">false</span>)
    processEvents<span class="hljs-literal">()</span>

    unlock(dmon.threadLock)

  <span class="hljs-type">CFRunLoopStop</span>(dmon.cfLoopRef)
  dmon.cfLoopRef = nil

proc unwatchState(watch: <span class="hljs-type">DmonWatchState</span>) =
  <span class="hljs-keyword">if</span> not watch.fsEvStreamRef.isNil:
    <span class="hljs-type">FSEventStreamStop</span>(watch.fsEvStreamRef)
    <span class="hljs-type">FSEventStreamInvalidate</span>(watch.fsEvStreamRef)
    <span class="hljs-type">FSEventStreamRelease</span>(watch.fsEvStreamRef)
    watch.fsEvStreamRef = nil

proc initDmon*<span class="hljs-literal">()</span> =
  <span class="hljs-keyword">assert</span>(not dmonInitialized)

  initLock(dmon.threadLock)
  dmon.threadSem = dispatch_semaphore_create(<span class="hljs-number">0</span>)
  <span class="hljs-keyword">assert</span>(not dmon.threadSem.isNil)

  createThread(dmon.threadHandle, monitorThread)

  discard dispatch_semaphore_wait(dmon.threadSem, <span class="hljs-type">DISPATCH_TIME_FOREVER</span>)

  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;<span class="hljs-number">64</span>:
    dmon.freeList[i] = <span class="hljs-number">64</span> - i - <span class="hljs-number">1</span>

  dmonInitialized = <span class="hljs-literal">true</span>

proc deinitDmon*<span class="hljs-literal">()</span> =
  <span class="hljs-keyword">assert</span>(dmonInitialized)

  dmon.quit = <span class="hljs-literal">true</span>
  joinThread(dmon.threadHandle)

  dispatch_release(dmon.threadSem)
  deinitLock(dmon.threadLock)

  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0.</span>.&lt;dmon.numWatches:
    <span class="hljs-keyword">if</span> dmon.watches[i] != nil:
      unwatchState(dmon.watches[i])

  dmon = <span class="hljs-type">DmonState</span><span class="hljs-literal">()</span>
  dmonInitialized = <span class="hljs-literal">false</span>

proc watchDmon*(rootdir: <span class="hljs-built_in">string</span>, watchCb: <span class="hljs-type">DmonWatchCallback</span>,
                flags: uint32, userData: pointer): <span class="hljs-type">DmonWatchId</span> =
  <span class="hljs-keyword">assert</span>(dmonInitialized)
  <span class="hljs-keyword">assert</span>(not rootdir.isEmptyOrWhitespace)
  <span class="hljs-keyword">assert</span>(watchCb != nil)

  dmon.modifyWatches.store(<span class="hljs-number">1</span>)
  withLock dmon.threadLock:
    <span class="hljs-keyword">assert</span>(dmon.numWatches &lt; <span class="hljs-number">64</span>)
    <span class="hljs-keyword">if</span> dmon.numWatches &gt;= <span class="hljs-number">64</span>:
      echo <span class="hljs-string">"Exceeding maximum number of watches"</span>
      return <span class="hljs-type">DmonWatchId</span>(<span class="hljs-number">0</span>)

    <span class="hljs-keyword">let</span> numFreeList = <span class="hljs-number">64</span> - dmon.numWatches
    <span class="hljs-keyword">let</span> index = dmon.freeList[numFreeList - <span class="hljs-number">1</span>]
    <span class="hljs-keyword">let</span> id = uint32(index + <span class="hljs-number">1</span>)

    <span class="hljs-keyword">if</span> dmon.watches[index] == nil:
      dmon.watches[index] = <span class="hljs-type">DmonWatchState</span><span class="hljs-literal">()</span>

    inc dmon.numWatches

    <span class="hljs-keyword">let</span> watch = dmon.watches[id - <span class="hljs-number">1</span>]
    watch.id = <span class="hljs-type">DmonWatchId</span>(id)
    watch.watchFlags = flags
    watch.watchCb = watchCb
    watch.userData = userData

    # <span class="hljs-type">Validate</span> directory
    <span class="hljs-keyword">if</span> not dirExists(rootdir):
      echo <span class="hljs-string">"Could not open/read directory: "</span>, rootdir
      dec dmon.numWatches
      return <span class="hljs-type">DmonWatchId</span>(<span class="hljs-number">0</span>)

    # <span class="hljs-type">Handle</span> symlinks
    var finalPath = rootdir
    <span class="hljs-keyword">if</span> (flags <span class="hljs-keyword">and</span> uint32(<span class="hljs-type">DmonWatchFlags</span>.<span class="hljs-type">FollowSymlinks</span>)) != <span class="hljs-number">0</span>:
      <span class="hljs-keyword">try</span>:
        finalPath = expandSymlink(rootdir)
      except <span class="hljs-type">OSError</span>:
        echo <span class="hljs-string">"Failed to resolve symlink: "</span>, rootdir
        dec dmon.numWatches
        return <span class="hljs-type">DmonWatchId</span>(<span class="hljs-number">0</span>)

    # <span class="hljs-type">Setup</span> watch path
    watch.rootdir = finalPath.normalizedPath
    <span class="hljs-keyword">if</span> not watch.rootdir.endsWith(<span class="hljs-string">"/"</span>): 
      watch.rootdir.add <span class="hljs-string">"/"</span>

    watch.rootdirUnmod = watch.rootdir
    watch.rootdir = watch.rootdir.toLowerAscii

    # <span class="hljs-type">Create</span> <span class="hljs-type">FSEvents</span> stream
    <span class="hljs-keyword">let</span> cfPath = <span class="hljs-type">CFStringCreateWithCString</span>(nil, watch.rootdirUnmod.cstring, kCFStringEncodingUTF8)
    defer: <span class="hljs-type">CFRelease</span>(cfPath)

    <span class="hljs-keyword">let</span> cfPaths = <span class="hljs-type">CFArrayCreate</span>(nil, cast[ptr pointer](unsafeAddr cfPath), <span class="hljs-number">1</span>, nil)
    defer: <span class="hljs-type">CFRelease</span>(cfPaths)

    var ctx = <span class="hljs-type">FSEventStreamContext</span>(
      version: <span class="hljs-number">0</span>,
      info: cast[pointer](id),
      retain: nil,
      release: nil,
      copyDescription: nil
    )

    <span class="hljs-keyword">let</span> streamFlags = <span class="hljs-type">FSEventStreamEventFlags</span>(kFSEventStreamCreateFlagFileEvents)

    watch.fsEvStreamRef = <span class="hljs-type">FSEventStreamCreate</span>(
      nil,                              # <span class="hljs-type">Use</span> default allocator
      fsEventCallback,                  # <span class="hljs-type">Callback</span> <span class="hljs-keyword">function</span>
      addr ctx,                         # <span class="hljs-type">Context</span> <span class="hljs-keyword">with</span> watch <span class="hljs-type">ID</span>
      cfPaths,                         # <span class="hljs-type">Array</span> <span class="hljs-keyword">of</span> paths <span class="hljs-keyword">to</span> watch
      <span class="hljs-type">FSEventStreamEventId</span>(<span class="hljs-number">0</span>),         # <span class="hljs-type">Start</span> from now
      <span class="hljs-number">0.25</span>,                           # <span class="hljs-type">Latency</span> <span class="hljs-keyword">in</span> seconds
      streamFlags                     # <span class="hljs-type">File</span>-level events
    )

    <span class="hljs-keyword">if</span> watch.fsEvStreamRef.isNil:
      echo <span class="hljs-string">"Failed to create FSEvents stream"</span>
      dec dmon.numWatches
      return <span class="hljs-type">DmonWatchId</span>(<span class="hljs-number">0</span>)

    dmon.modifyWatches.store(<span class="hljs-number">0</span>)
    result = <span class="hljs-type">DmonWatchId</span>(id)

proc unwatchDmon*(id: <span class="hljs-type">DmonWatchId</span>) =
  <span class="hljs-keyword">assert</span>(dmonInitialized)
  <span class="hljs-keyword">assert</span>(uint32(id) &gt; <span class="hljs-number">0</span>)

  <span class="hljs-keyword">let</span> index = <span class="hljs-built_in">int</span>(uint32(id) - <span class="hljs-number">1</span>)
  <span class="hljs-keyword">assert</span>(index &lt; <span class="hljs-number">64</span>)
  <span class="hljs-keyword">assert</span>(dmon.watches[index] != nil)
  <span class="hljs-keyword">assert</span>(dmon.numWatches &gt; <span class="hljs-number">0</span>)

  <span class="hljs-keyword">if</span> dmon.watches[index] != nil:
    dmon.modifyWatches.store(<span class="hljs-number">1</span>)
    withLock dmon.threadLock:
      unwatchState(dmon.watches[index])
      dmon.watches[index] = nil

      dec dmon.numWatches
      <span class="hljs-keyword">let</span> numFreeList = <span class="hljs-number">64</span> - dmon.numWatches
      dmon.freeList[numFreeList - <span class="hljs-number">1</span>] = index

    dmon.modifyWatches.store(<span class="hljs-number">0</span>)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Unlocking the Power of CSS: Transforming Figuro with Seamless Theming]]></title><description><![CDATA[The Problem of Theming
After a couple of years of working on Figuro, a framework for building custom GUI apps with small self-contained binaries, I finally decided it was time to add theming to it. There’s something pleasing about being able to theme...]]></description><link>https://blog.elcritch.net/why-i-settled-on-css-for-theming-in-figuro</link><guid isPermaLink="true">https://blog.elcritch.net/why-i-settled-on-css-for-theming-in-figuro</guid><category><![CDATA[Figuro]]></category><category><![CDATA[graphical user interface]]></category><category><![CDATA[CSS]]></category><category><![CDATA[UX]]></category><category><![CDATA[Nim]]></category><category><![CDATA[DOM]]></category><category><![CDATA[GUI]]></category><dc:creator><![CDATA[Jaremy Creechley]]></dc:creator><pubDate>Tue, 14 Jan 2025 03:58:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736828089677/06c10844-830e-4067-be31-53182621715c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem-of-theming">The Problem of Theming</h2>
<p>After a couple of years of working on <a target="_blank" href="https://github.com/elcritch/figuro">Figuro</a>, a framework for building custom GUI apps with small self-contained binaries, I finally decided it was time to add theming to it. There’s something pleasing about being able to theme a GUI application while it’s running. It’s one of the great secrets behind HTML’s success. It also solves one of the bigger pain points in Figuro which was making it easier to develop both customizable <em>and</em> re-usable widgets.</p>
<p>Still it wasn’t clear how best to add theming. Would it be best to make a custom theming file format using Figuro’s properties and syntax? Or to build on something else? Perhaps it’d be possible to create a Nim based DSL for theming. These thoughts and questions lingered for a few months as I let my thoughts simmer on the topic.</p>
<p>Eventually I decided that creating a custom theming DSL would also require defining the <em>semantics</em> for the DSL as well. It’s hard enough developing the core of a GUI system, but creating a useful theming language on top would be even more work. Also building on Nim’s VM to borrow Figuro’s syntax for a DSL is great for development but would require embedding the Nim compiler as well which is less appealing – despite the compilers compact size.</p>
<h2 id="heading-styling-widgets-from-the-outside">Styling Widgets from the Outside</h2>
<p>Let’s go back and check out some of the issues with making style-able widgets. Take this snippet of Figuro’s Button widget to see some of the limitations with styling widgets:</p>
<pre><code class="lang-ocaml"><span class="hljs-keyword">type</span>
  <span class="hljs-type">Button</span>*[<span class="hljs-type">T</span>] = <span class="hljs-built_in">ref</span> <span class="hljs-keyword">object</span> <span class="hljs-keyword">of</span> <span class="hljs-type">StatefulFiguro</span>[<span class="hljs-type">T</span>]
    label*: <span class="hljs-built_in">string</span>
    disabled*: <span class="hljs-built_in">bool</span>
    clickMode*: set[<span class="hljs-type">ButtonClicks</span>] = {<span class="hljs-type">Single</span>}

... 

proc draw*[<span class="hljs-type">T</span>](self: <span class="hljs-type">Button</span>[<span class="hljs-type">T</span>]) {.slot.} =
  ## button widget!
  <span class="hljs-keyword">with</span> self:
    clipContent <span class="hljs-literal">true</span>
  withOptional self:
    cornerRadius <span class="hljs-number">10.0</span><span class="hljs-symbol">'ui</span>

  <span class="hljs-keyword">if</span> self.disabled:
    withOptional self:
      fill css<span class="hljs-string">"#F0F0F0"</span>
  <span class="hljs-keyword">else</span>:
    withOptional self:
      fill css<span class="hljs-string">"#2B9FEA"</span>
    self.onHover:
      withOptional self:
        fill self, self.fill.lighten(<span class="hljs-number">0.14</span>)
        # this changes the color on hover!
</code></pre>
<p>In this code you can see that styling a node is really easy in Figuro. However, making it so a user of the widget can style the Button required adding constructs such as <code>withOptional self:</code> which only changes the node’s color if the user hasn’t already set that particular field. For example:</p>
<pre><code class="lang-ocaml">          <span class="hljs-type">Button</span>[<span class="hljs-built_in">int</span>].<span class="hljs-keyword">new</span>(<span class="hljs-string">"btn"</span>):
            <span class="hljs-keyword">let</span> btn = node
            <span class="hljs-keyword">with</span> node:
              size <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>, <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>
              fill css<span class="hljs-string">"red"</span>
              connect(doHover, self, btnHover)
              connect(doClick, node, btnClicked)
            <span class="hljs-type">Text</span>.<span class="hljs-keyword">new</span> <span class="hljs-string">"text"</span>:
              <span class="hljs-keyword">with</span> node:
                fill blackColor
                setText({font: $(btn.state)}, <span class="hljs-type">Center</span>, <span class="hljs-type">Middle</span>)
</code></pre>
<p>This requires widgets to be written differently than writing application code. That didn’t sit well with me. For various technical reasons a widget’s draw method (slot) executes <em>after</em> the user’s code. This allows the user to configure size and other important parameters. However, whether the user’s code is run before or after the widget’s you end up with an annoying ordering issue.</p>
<p>Briefly I experimented with <em>pre</em> and <em>post</em> blocks, but this didn’t <em>feel</em> ideal. It moves Figuro away from it’s “enlightened” mixed of both <em>declarative</em> and <em>imperative</em> style syntax. Writing applications requires more knowledge of <em>when</em> and <em>where</em> to properly add things.</p>
<pre><code class="lang-ocaml">          <span class="hljs-type">Button</span>[<span class="hljs-built_in">int</span>].<span class="hljs-keyword">new</span>(<span class="hljs-string">"btn"</span>):
            size <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>, <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>
            node.connect(doHover, self, btnHover)
            node.connect(doClick, node, btnClicked)
            <span class="hljs-type">Text</span>.<span class="hljs-keyword">new</span> <span class="hljs-string">"text"</span>:
              node.setText({font: $(btn.state)}, <span class="hljs-type">Center</span>, <span class="hljs-type">Middle</span>)
              ...
          finally:
            node.size <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>, <span class="hljs-number">100</span><span class="hljs-symbol">'ux</span>
            node.fill css<span class="hljs-string">"red"</span>
</code></pre>
<h2 id="heading-enter-css">Enter CSS</h2>
<p>Figuro takes it’s original design from <a target="_blank" href="https://github.com/treeform/fidget">Fidget</a> and builds upon (and significantly improves) that original design. Fidget had some awesome ideas on code interacting with Figma. Still at it’s heart, Figuro is based on Nodes at its core.</p>
<p>Each node is simple with only a few properties. The API is less complex than the DOM at the heart of HTML. Despite being simpler than DOM nodes, Figuro Nodes still form a <a target="_blank" href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">DAG</a> of UI nodes each with a common set of properties.</p>
<p>That means Cascading Style Sheets (CSS) makes a good fit for styling UIs written with Figuro. Traditional GUI toolkits diverge significantly from HTML’s node tree as a UI. Of course they can still use CSS it’s just not quite as natural a fit in my view.</p>
<p>Being able to define a CSS rule for the Button widget becomes very simple in CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">Button</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">164</span>, <span class="hljs-number">158</span>, <span class="hljs-number">255</span>);
  <span class="hljs-attribute">border-width</span>: <span class="hljs-number">5</span>;
  <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#160075</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">78</span>, <span class="hljs-number">0</span>, <span class="hljs-number">90</span>);
  <span class="hljs-attribute">box-shadow</span>: none;
}

<span class="hljs-selector-tag">Button</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">158</span>, <span class="hljs-number">223</span>, <span class="hljs-number">255</span>);
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">5px</span> <span class="hljs-number">2px</span> <span class="hljs-number">4px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">47</span>, <span class="hljs-number">0</span>, <span class="hljs-number">75</span>, <span class="hljs-number">0.119</span>);
}
</code></pre>
<p>Now our buttons go from this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736826922146/a5f48105-1f96-42e1-95cd-b9203121b812.png" alt class="image--center mx-auto" /></p>
<p>To this without any code changes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736826970214/46ebaf7d-8a6e-4801-bf69-f58f1e3da424.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item></channel></rss>