
    <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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
      <channel>
        <title>Sam Roberts' personal website</title>
        <description>Sam Roberts' personal website</description>
        <link>https://samgqroberts.com</link>
        <language>en</language>
        <lastBuildDate>Mon, 27 Apr 2026 01:01:31 GMT</lastBuildDate>
        <atom:link href="https://samgqroberts.com" rel="self" type="application/rss+xml" />
        
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/adapting-my-rust-terminal-ui-game-to-run-in-the-browser</guid>
      <title>Adapting my Rust terminal UI game to run in the browser</title>
      <description>A few months ago I published Merchant, a terminal UI game written in Rust. Since then I’ve gotten it to work in the browser in a way that shares all the game logic with the terminal version.</description>
      <link>https://samgqroberts.com/posts/adapting-my-rust-terminal-ui-game-to-run-in-the-browser</link>
      <pubDate>Mon, 27 Apr 2026 00:10:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>A few months ago I published Merchant, a terminal UI game written in Rust. Since then I’ve gotten it to work in the browser in a way that shares all the game logic with the terminal version.</p>
<p>TL;DR: <a href="https://merchant.samgqroberts.com">check it out</a> (note: it’s a little small on mobile)</p>
<h2><a name="p-41-where-we-started-1" class="anchor" href="#p-41-where-we-started-1"></a>Where we started</h2>
<p>You can read <a href="https://samgqroberts.com/posts/merchant">my initial post</a> on Merchant if you want the full backstory, but the important technical pieces are:</p>
<ul>
<li>We used <a href="https://docs.rs/crossterm/latest/crossterm/">crossterm</a> as the terminal / ANSI library, enabling us to target it and easily get Windows / Mac / Linux compatibility.</li>
<li>We built a rendering engine with the following loop:
<ol>
<li>Wipe the screen</li>
<li>Translate a <code>GameState</code> object into a large sequence of crossterm commands</li>
<li>Write those commands to the terminal to display the scene</li>
<li>Await user input</li>
<li>Update the <code>GameState</code></li>
</ol>
</li>
</ul>
<p>The important layers of abstraction looked like:</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/aba87066810d1bb53930dc4db5071b9ee3dab518.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/aba87066810d1bb53930dc4db5071b9ee3dab518" title="b2966c91ffd1aff71f44dcd04627301316500300d0c341470693328c3a320cbf807d771f4686adc0d31cc10b44a65f468423c1d6b775e76c60c2763c8a5693e4"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/aba87066810d1bb53930dc4db5071b9ee3dab518_2_602x500.png" alt="b2966c91ffd1aff71f44dcd04627301316500300d0c341470693328c3a320cbf807d771f4686adc0d31cc10b44a65f468423c1d6b775e76c60c2763c8a5693e4" data-base62-sha1="ouywDMgCeMQBBpzjWR5gMT4jp3O" width="602" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/aba87066810d1bb53930dc4db5071b9ee3dab518_2_602x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/aba87066810d1bb53930dc4db5071b9ee3dab518_2_903x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/aba87066810d1bb53930dc4db5071b9ee3dab518_2_1204x1000.png 2x" data-dominant-color="ECECEC"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">b2966c91ffd1aff71f44dcd04627301316500300d0c341470693328c3a320cbf807d771f4686adc0d31cc10b44a65f468423c1d6b775e76c60c2763c8a5693e4</span><span class="informations">1232×1022 58 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<h2><a name="p-41-how-do-we-support-the-web-2" class="anchor" href="#p-41-how-do-we-support-the-web-2"></a>How do we support the web?</h2>
<p>I’m sure there are other, potentially easier ways to accomplish this. I took shallow looks at options like <a href="https://xtermjs.org/">xterm.js</a> for a kind of “drop-in” solution, but it was a half-hearted search. I wasn’t convinced I would be able to get exactly what I wanted, especially considering a potential future mobile-friendly browser version, and besides I started this project to dive deeper into Rust.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/52be3d64889303680dd669fc6ec160c259b3f66e.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/52be3d64889303680dd669fc6ec160c259b3f66e" title="d5a498d8bd5da1c9cac52b1ce98b2ae7d4543ddfc81741cab00c7d28fdf8e842cabd2e0c77cc0038da1ff33dd94d5100a0255eecb70f6db3da7a38284d6ae832"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/52be3d64889303680dd669fc6ec160c259b3f66e_2_210x500.png" alt="d5a498d8bd5da1c9cac52b1ce98b2ae7d4543ddfc81741cab00c7d28fdf8e842cabd2e0c77cc0038da1ff33dd94d5100a0255eecb70f6db3da7a38284d6ae832" data-base62-sha1="bNYJQDRbiBHmDSVlkSQ5gWgjRZI" width="210" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/52be3d64889303680dd669fc6ec160c259b3f66e_2_210x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/52be3d64889303680dd669fc6ec160c259b3f66e_2_315x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/52be3d64889303680dd669fc6ec160c259b3f66e_2_420x1000.png 2x" data-dominant-color="EEEEEF"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">d5a498d8bd5da1c9cac52b1ce98b2ae7d4543ddfc81741cab00c7d28fdf8e842cabd2e0c77cc0038da1ff33dd94d5100a0255eecb70f6db3da7a38284d6ae832</span><span class="informations">620×1470 66.8 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<h2><a name="p-41-some-rust-can-run-in-the-browser-3" class="anchor" href="#p-41-some-rust-can-run-in-the-browser-3"></a>(Some) Rust can run in the browser!</h2>
<p>I decided to write whatever I was going to make to target wasm, via <a href="https://wasm-bindgen.github.io/wasm-bindgen/">wasm-bindgen</a>. My first foray into this involved trying to intercept the crossterm commands output by my engine and render them differently. Something that looked like this:</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/9dc0671b7d0039f5059fa32fc6789d5d86338fcc.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/9dc0671b7d0039f5059fa32fc6789d5d86338fcc" title="d3fda906909d85cebf40ed69348aa9765697591fa824cd6fcbfd1e9d5903f7e387d2e11759c0826a95ed78f465903a129524307b37b819cfe0969b1f7f4e6d0f"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9dc0671b7d0039f5059fa32fc6789d5d86338fcc_2_690x430.png" alt="d3fda906909d85cebf40ed69348aa9765697591fa824cd6fcbfd1e9d5903f7e387d2e11759c0826a95ed78f465903a129524307b37b819cfe0969b1f7f4e6d0f" data-base62-sha1="mvxboPD6yXtajJcESWvoA9jfPCQ" width="690" height="430" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9dc0671b7d0039f5059fa32fc6789d5d86338fcc_2_690x430.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9dc0671b7d0039f5059fa32fc6789d5d86338fcc_2_1035x645.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9dc0671b7d0039f5059fa32fc6789d5d86338fcc_2_1380x860.png 2x" data-dominant-color="F0F0F1"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">d3fda906909d85cebf40ed69348aa9765697591fa824cd6fcbfd1e9d5903f7e387d2e11759c0826a95ed78f465903a129524307b37b819cfe0969b1f7f4e6d0f</span><span class="informations">1388×866 85.3 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Our game engine already targeted the crossterm abstractions, and I could easily imagine a separate rendering engine that took the sequence of crossterm commands and instead of printing them to a terminal with fancy ANSI sequences, updated some DOM element. Eg. the renderer would maintain a grid layout in html and update it similarly to the character grid of the terminal.</p>
<p>Unfortunately, I was met with an issue: crossterm has stuff that can’t compile with a wasm target. After all it was built for OS terminals, so this should have been easy to predict.</p>
<p>I then optimistically wondered if there was a way to configure the crossterm dependency to only give me the command interfaces, without the wasm-incompatible terminal stuff under them? Then my core game logic could only bring in that, the terminal project could bring in the full crossterm dependency, and my web project would be free to make its own renderer based on the crossterm commands emitted by my core game logic.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/3da7bdc796b9aa8d2873ad69f46584ce065e286e.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/3da7bdc796b9aa8d2873ad69f46584ce065e286e" title="60ffac36d73a4191c1b1890114687bfbc84eb1362a35afd85d8776b906c66a62bdadaa1bb966f0cff5f10bc8b2224554e87c702aa1d955e6ba6bda4d19dfcfc4"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/3da7bdc796b9aa8d2873ad69f46584ce065e286e_2_481x500.jpeg" alt="60ffac36d73a4191c1b1890114687bfbc84eb1362a35afd85d8776b906c66a62bdadaa1bb966f0cff5f10bc8b2224554e87c702aa1d955e6ba6bda4d19dfcfc4" data-base62-sha1="8NqvBNlFvv5MkP7GbAEma2PSBpc" width="481" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/3da7bdc796b9aa8d2873ad69f46584ce065e286e_2_481x500.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/3da7bdc796b9aa8d2873ad69f46584ce065e286e_2_721x750.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/3da7bdc796b9aa8d2873ad69f46584ce065e286e_2_962x1000.jpeg 2x" data-dominant-color="F0F1F1"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">60ffac36d73a4191c1b1890114687bfbc84eb1362a35afd85d8776b906c66a62bdadaa1bb966f0cff5f10bc8b2224554e87c702aa1d955e6ba6bda4d19dfcfc4</span><span class="informations">1442×1498 140 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Turns out you can’t do that either, or at least I didn’t figure out the right cargo feature incantation. It looked like having the web project depend on crossterm at all was a no-go.</p>
<h2><a name="p-41-fine-ill-build-my-own-crossterm-4" class="anchor" href="#p-41-fine-ill-build-my-own-crossterm-4"></a>Fine, I’ll build my own crossterm</h2>
<p>With that failure, I figured I had to go even one more step up.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9" title="6ad67a9618a579b974dd94d47ba4f597da3a1020ea5dc018f3d81af3b555f06c0b781d95d260a26a95eda037b1b408a209deab60195a529390de2cd0f9e64591"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9_2_488x500.jpeg" alt="6ad67a9618a579b974dd94d47ba4f597da3a1020ea5dc018f3d81af3b555f06c0b781d95d260a26a95eda037b1b408a209deab60195a529390de2cd0f9e64591" data-base62-sha1="s4SGub5CIlc2KgBRYZa3Z4sR2Dn" width="488" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9_2_488x500.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9_2_732x750.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/c4ca2600fd452dc29e79f3d338cebb4b6a7f2be9_2_976x1000.jpeg 2x" data-dominant-color="EFF1F4"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">6ad67a9618a579b974dd94d47ba4f597da3a1020ea5dc018f3d81af3b555f06c0b781d95d260a26a95eda037b1b408a209deab60195a529390de2cd0f9e64591</span><span class="informations">1410×1442 164 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>That’s what I ended up doing. I created a small subcrate in my Merchant workspace called <a href="https://github.com/samgqroberts/merchant/tree/f253ac4cb0229065fe65b5240e6402f4d5f6bf88/terminal-commands">terminal-commands</a> that looked like a copy of the Commands offered by crossterm. I then rewrote my game engine to target <em>that</em> interface, which was very easy because that interface looks almost identical to crossterm commands.</p>
<pre data-code-wrap="rust"><code class="lang-rust">// before
use std::io::{self, Write};

use crossterm::{queue, cursor::MoveTo, style::Print};

pub fn hello_world(writer: &amp;mut impl Write) -&gt; io::Result&lt;()&gt; {
  queue!(writer, MoveTo(0, 0), Print(“hello world”))
}

// after
use std::io::{self, Write};

use terminal_commands::{comp, Commands, cursor::MoveTo, style::Print}; // &lt;— my own crate

pub fn hello_world(commands: &amp;mut Commands) -&gt; Result&lt;(), String&gt; {
  comp!(commands, MoveTo(0, 0), Print(“hello world”))
}
</code></pre>
<ul>
<li>Instead of importing the commands (<code>MoveTo</code>, <code>Print</code>) from crossterm, we import them from <code>terminal_commands</code>, which has maintained crossterm’s mod structure to make this refactor easy.</li>
<li>Instead of using crossterm’s <code>queue!</code> macro to write to a <code>std::io::Write</code> target, we write to our own <code>Commands</code> type (which is just a vector of commands, storing the sequence for later rendering) with our own <code>comp!</code> macro, which just makes it easier to push to the Commands Vec.</li>
</ul>
<p>The core game logic translates the GameState structure into a big sequence of commands in this Commands Vec, and the some platform-specific engine can now pass that vector to a platform-specific renderer.</p>
<p>The <a href="https://github.com/samgqroberts/merchant/blob/f253ac4cb0229065fe65b5240e6402f4d5f6bf88/terminal/src/renderer.rs">terminal renderer</a> is extremely straightforward, since the commands were modeled off of crossterm’s commands, so the renderer basically just does a 1:1 translation.</p>
<p>The <a href="https://github.com/samgqroberts/merchant/blob/f253ac4cb0229065fe65b5240e6402f4d5f6bf88/web/src/html_renderer.rs">html renderer</a> is more interesting. This had to figure out a way to take coordinate-based commands like MoveTo(x, y) and translate them into some location on an html element, and then print possibly styled characters (moving 1 coordinate spot to the right each character).</p>
<p>The approach I ended up going with was to create a rectangular grid by defining each coordinate point with a span that contained either a character or a <code>nbsp;</code>. These character spans had styling attached to them that enforced that each character’s width was exactly <code>1ch</code>, which for higher-order unicode characters was not reliably happening on mobile even with a monospaced font.</p>
<h2><a name="p-41-handling-input-5" class="anchor" href="#p-41-handling-input-5"></a>Handling input</h2>
<p>The next part of the interface that needed to diverge based on environment was input handling.</p>
<p>The game engine relies on simple single-key input (eg. pressing “b” to navigate into the “buy” screen), including non-character keys like escape, as well as multiple digit input committed with enter (eg. entering how much of something you’d like to buy).</p>
<p>The same approach applies here: the <code>terminal_commands</code> subcrate has a KeyEvent struct, emulating the crossterm KeyEvent struct, and both terminal and html engines convert their environment’s interactions into that interface, then the game engine can handle it in the same way after that.</p>
<h2><a name="p-41-and-supporting-mobile-6" class="anchor" href="#p-41-and-supporting-mobile-6"></a>And supporting mobile</h2>
<p>This required sprinkling some magic dust on top of the web interface. We can already render on mobile the same way we render on desktop, since it’s in the browser, but how should we handle input?</p>
<p>I considered more fancier techniques, like in the game engine annotating certain UI elements as a “button”, so that the web renderer could allow users to click directly on them instead of triggering their action via keyboard input. That would have been cool, and if what I built here was going to evolve into a publishable library that would be a great direction to move in, but I decided to keep things more in-line with the terminal environment and figure out direct keyboard input.</p>
<p>This meant telling the mobile device that it should have its keyboard open, but not necessarily showing the user a text input element. We achieve this by adding a hidden <code>&lt;input /&gt;</code> element to the DOM <a href="https://github.com/samgqroberts/merchant/blob/f253ac4cb0229065fe65b5240e6402f4d5f6bf88/web/merchant-web/index.html#L85">here</a>, and listening to keyboard events on it <a href="https://github.com/samgqroberts/merchant/blob/f253ac4cb0229065fe65b5240e6402f4d5f6bf88/web/src/lib.rs#L71">here</a>. Plus adding a whole bunch of annoying defensiveness because sometimes more input events trigger on mobile than on desktop for a single keypress.</p>
<h2><a name="p-41-conclusion-7" class="anchor" href="#p-41-conclusion-7"></a>Conclusion</h2>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/a4dfd0aa45456d65ce61e21764e953315123e232.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/a4dfd0aa45456d65ce61e21764e953315123e232" title="Screenshot 2026-04-26 at 20.09.18"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/a4dfd0aa45456d65ce61e21764e953315123e232_2_589x500.png" alt="Screenshot 2026-04-26 at 20.09.18" data-base62-sha1="nwxPgbawwc6w1dSupSwwYw4gfWG" width="589" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/a4dfd0aa45456d65ce61e21764e953315123e232_2_589x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/a4dfd0aa45456d65ce61e21764e953315123e232_2_883x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/a4dfd0aa45456d65ce61e21764e953315123e232_2_1178x1000.png 2x" data-dominant-color="272727"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Screenshot 2026-04-26 at 20.09.18</span><span class="informations">1796×1524 167 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>What we ended up with is a working game in the browser, powered by Rust / Wasm, sharing all game logic with the terminal version by way of an interesting little bespoke library. The tool is very niche, certainly, but as always the journey was the reward. I learned a lot, got to expand my familiarity with Rust which I always appreciate, and now I can send my non-programmer friends a simple URL to play my game rather than a set of scary instructions on how to open and use their terminal.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/merchant</guid>
      <title>Merchant</title>
      <description>I built an homage to Drug Wars, the classic terminal game, in Rust, my favorite programming language these days.</description>
      <link>https://samgqroberts.com/posts/merchant</link>
      <pubDate>Fri, 25 Jul 2025 16:23:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>I built an homage to Drug Wars, the classic terminal game, in Rust, my favorite programming language these days.</p>
<p>TL;DR: follow instructions in this <a href="https://github.com/samgqroberts/merchant?tab=readme-ov-file#how-to-install-and-play-the-game">README</a> to play a game in your terminal that looks like this:</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/db765f98a3bb99bb353b48031fb07d31716a2f85.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/db765f98a3bb99bb353b48031fb07d31716a2f85" title="Screenshot 2025-05-30 at 12.11.03 PM"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/db765f98a3bb99bb353b48031fb07d31716a2f85_2_664x500.jpeg" alt="Screenshot 2025-05-30 at 12.11.03 PM" data-base62-sha1="vjsb9Bi1boL2B6hkzktHGDIQisR" width="664" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/db765f98a3bb99bb353b48031fb07d31716a2f85_2_664x500.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/db765f98a3bb99bb353b48031fb07d31716a2f85_2_996x750.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/db765f98a3bb99bb353b48031fb07d31716a2f85_2_1328x1000.jpeg 2x" data-dominant-color="5D5C5C"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Screenshot 2025-05-30 at 12.11.03 PM</span><span class="informations">1550×1166 207 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<h2><a name="p-34-gathering-provisions-1" class="anchor" href="#p-34-gathering-provisions-1"></a>Gathering Provisions</h2>
<p>In the spring of 2021 I was focused on expanding my understanding of programming languages. The previous few years saw me ditch Javascript for fully embracing TypeScript as well as dabble with more functional languages like Elm, and a growing language called <a href="https://www.unison-lang.org/">Unison</a>. I was working my way through <a href="https://haskellbook.com/">Haskell Programming from First Principles</a> when I first heard about Rust.</p>
<p>A smart, trusted coworker had fallen in love with it and strongly recommended I check it out. I started messing around with tutorials and found that the design decisions (especially the type system) really resonated with me.</p>
<p>After about a year and a half I found a legitimate use case for it in some work I was doing for a client. We wanted to build a <a href="https://redis.io/docs/latest/develop/reference/modules/">custom Redis module</a>, which was usually done in C / C++ but also had official <a href="https://github.com/RedisLabsModules/redismodule-rs">Rust bindings</a>. We didn’t have particular experience in either, so we took a bet on Rust.</p>
<p>Since that fall of 2022, our trust in Rust and its presence in that codebase steadily grew from that Redis module being called from our Python HTTP server application, to wrapping core Rust logic in PyO3 and calling it directly from the Python, to finally deciding to rewrite all the existing Python in Rust. We now have 80k lines of application and test Rust, and 0 lines of Python, and it feels <em>great</em>.</p>
<p>At the same time in early 2023 ChatGPT exploded in popularity, and with it the general notoriety of LLMs. After my first few conversations with it <a href="https://samgqroberts.com/posts/learning-by-playing">of course</a> I started thinking about how it could be incorporated in a game experience.</p>
<h2><a name="p-34-setting-sail-2" class="anchor" href="#p-34-setting-sail-2"></a>Setting Sail</h2>
<p>With the desire to gain experience with Rust and ChatGPT serving as my personal backdrop, I made the <a href="https://github.com/samgqroberts/merchant/commit/a274098810f7424200e1683fe9c02c1677a84f37">first commit</a> on this new project June 18, 2023. My goal was to sprint to a complete, playable game first, and then use that as a foundation to experiment with LLM integrations. I decided to basically make a clone of <a href="https://en.wikipedia.org/wiki/Drug_Wars_(video_game)">Drug Wars</a> because:</p>
<ol>
<li>I loved playing it on my TI-84 calculator in high school.</li>
<li>It has a simple but entertaining set of mechanics which I knew well.</li>
<li>It’s a terminal UI game, meaning I could preserve some focus in my personal education by avoiding more complex graphics stuff.</li>
<li>I could already imagine interesting LLM integrations for it, like being able to chat with NPCs to get hints as to where good deals would be.</li>
</ol>
<p>On the technology side, I adopted the Rust terminal UI library <a href="https://docs.rs/crossterm/0.29.0/crossterm/">crossterm</a> as the primary framework, which I found to have a decently easy API and gave me a product that works cross-platform on Mac, Linux, and Windows pretty much for free.</p>
<p>On top of <code>crossterm</code> I built a small rendering and state engine that is based around a main loop that pretty much works like this:</p>
<ol>
<li>Wipe the entire screen.</li>
<li>Feed the current <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/game/src/state/game_state.rs#L112">GameState</a>, a big struct that contains every bit of data the game tracks, into a <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/game/src/engine.rs#L109">render function</a> that prints out the entire frame.</li>
<li>Await user keyboard input, at which point update the state accordingly</li>
<li>Repeat</li>
</ol>
<p>I’m a big believer in automated testing, especially for these little side projects that you might come back to once every three months and inevitably forget all the invariants you need to maintain, so I also wrote a little <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/game/src/test/test_engine.rs">integration testing framework</a> for the game. It simulates the main loop and captures the rendered output, allowing assertions on the entire frame. Here’s <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/game/src/test/integration_cases.rs#L17">an example</a> that just tests that you can press some key on the splash screen to progress to the main game scene.</p>
<p>The trickiest part about the testing framework came from the fact that crossterm uses <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape sequences</a> to position and format text in the rendered terminal output (which is great, that’s how you do it). But I didn’t want to test that some rendered output was something like <code>\x1b[1mH\033[5CW\033[22m\033[6Dello\033[2Corld</code>, which would look to the user like “<strong>H</strong>ello <strong>W</strong>orld” in the terminal, since that would be way too brittle and almost certainly cross some testing cost-benefit threshold where I would give up on testing altogether. I figured it was more important to test the characters (including their ordering) and less important to test styling, so here I would want to assert that this rendered output matches the regular UTF-8 string <code>Hello World</code>. So I built <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/raw_format_ansi/src/lib.rs">a small separate crate</a> that does just that - it takes a string with ANSI escape sequences, ignores the text styling codes, and interprets the cursor positioning codes to produce that UTF-8 output.</p>
<h2><a name="p-34-port-of-arrival-3" class="anchor" href="#p-34-port-of-arrival-3"></a>Port of arrival</h2>
<p>It turns out, as always, building something to completion always takes much longer than I think. I chose a simple and straightforward project, so, in and out in 15 minutes? How about almost 2 years (of very intermittent development). It’s taken about 100 commits to get to this place where I feel like it’s ready to show people. Squinting at the commit history and remembering the past couple years, I’m guessing I could have knocked this out in maybe a couple full work weeks. But finding that time for a side project I guess is the real challenge.</p>
<p>In any case, what we ended up with 2 years later is what I’ve called Merchant. It’s basically a reskin of Drug Wars. The mechanics are almost entirely the same but you deal in goods like tea instead of drugs, you store those goods in a ship’s hold instead of a trench coat, and you’re navigating 18th century trading ports instead of modern day New York. Overall, it’s just a little more family friendly.</p>
<p>I took a few liberties with the mechanics beyond strictly adhering to those of Drug Wars. For example, I introduced this idea of “personalities” for each location, randomly generated and assigned for each playthrough. The personality affects the variance in price distributions as well as random event probabilities, so there’s a reason not to just go back and forth between 2 different ports for the whole game. Also, since your home port has additional features like access to the bank and stash, its personality is set to be a little more “boring”. I’m not sure if the original game did this or not, but I also did some balancing things like making sure that the random event where you find free goods is at least somewhat based on current game state so you don’t suddenly 50000x your net worth.</p>
<h2><a name="p-34-exporting-the-goods-4" class="anchor" href="#p-34-exporting-the-goods-4"></a>Exporting the goods</h2>
<p>It was pretty fun figuring out how to distribute the game. Obviously people can go pull down the <a href="https://github.com/samgqroberts/merchant">Github repo</a> and launch it with <code>cargo run</code>. But I wanted it to be as easy as possible for people to check the game out.</p>
<p>Luckily, I found <a href="https://github.com/axodotdev/cargo-dist">cargo-dist</a>, or I guess now just <code>dist</code>. It’s fantastic. What I had to do was provide <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/dist-workspace.toml">this small configuration file</a>, set up <a href="https://github.com/samgqroberts/homebrew-tap">a homebrew tap repository</a> for Homebrew distribution, set up an NPM account and <a href="https://www.npmjs.com/package/merchant-game">package</a>, and define auth tokens for each as repository secrets. <code>dist</code> generated and continuously manages <a href="https://github.com/samgqroberts/merchant/blob/v1.0.3/.github/workflows/release.yml">this complex github actions file</a>, and now whenever I tag a commit with a version string my Homebrew and NPM projects are updated, and a release like <a href="https://github.com/samgqroberts/merchant/releases/tag/v0.5.0">this</a> is all automatically generated. And as far as I can tell, it just works. Not bad, <code>dist</code>.</p>
<p>So go ahead and run <code>npx merchant-game</code> in your terminal, or <code>brew install samgqroberts/tap/merchant</code> then <code>merchant</code>, or use the Microsoft Installer, or <a href="https://github.com/samgqroberts/merchant/releases">download the binaries</a> directly!</p>
<h2><a name="p-34-future-horizons-5" class="anchor" href="#p-34-future-horizons-5"></a>Future horizons</h2>
<p>or, Wait what about ChatGPT?</p>
<p>Right, so, I never ended up doing any experiments with integrating an LLM. I’d like to release as-is for now, as it represents a product that can be considered finished, and it took enough time just to get to this point.</p>
<p>Now having achieved that first goal of a fully fleshed game, I still think it’s a fertile foundation for doing these LLM-based game design experiments. A couple ideas have been floating around. One builds on top of the location personality system, where interacting with an LLM might help give you clues as to where a good place to go might be, or a place to avoid. Like you might be able to visit something like a tavern and chat with people there, who based on your conversation with them might provide useful information.</p>
<p>Another bigger idea is kind of like a sequel to the game that I’d call Admiral, where instead of interacting with game state at the level you do in Merchant, you are instead at a higher organizational level, and you interact with a team of LLMs that are each captains that effectively play the Merchant game for you. You would receive reports and send out orders, perhaps always simply sitting at your admiral’s desk. Would that be fun? Would it work? Really unsure, but sounds interesting to explore.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/a-small-app-i-made-to-track-weightlifting-goals</guid>
      <title>A small app I made to track weightlifting goals</title>
      <description>I recently picked up weightlifting again and found myself wanting a particular way to visualize and contextualize my progress, so I prototyped a small app.</description>
      <link>https://samgqroberts.com/posts/a-small-app-i-made-to-track-weightlifting-goals</link>
      <pubDate>Mon, 26 Jun 2023 03:20:26 GMT</pubDate>
      <content:encoded><![CDATA[<p>I recently picked up weightlifting again and found myself wanting a particular way to visualize and contextualize my progress, so I prototyped a small app.</p>
<p><strong>TL;DR:</strong> <a href="https://lifting-goals.samgqroberts.com/">check it out</a>.</p>
<h2><a name="p-33-i-lift-1" class="anchor" href="#p-33-i-lift-1"></a>I lift?</h2>
<p>Coming out of college in 2014 I started messing around with weightlifting in the gym. The main motivation was just to hang out with friends who had gotten into it, so I never made all that much progress, but it always made me feel good and proud. As anyone who’s gotten into it could attest, there’s a particular, and particularly pleasing, chemical soup that permeates you when you do heavy resistance training.</p>
<p>Over the past decade I’ve come back to it in fits and starts. Always a great thing, but never really stuck, as it goes.</p>
<p>I hadn’t lifted in about 2 years when I started again this January. I was about to go away for two months on a physically demanding trip and I wanted to use that as motivation to get serious. As part of getting serious, I went about setting goals for myself. It turns out that open-ended progress just doesn’t keep me motivated, but setting some date and putting it in my calendar, even if only I know about it, goes a long way to keep me honest. In fact, a very similar calendar entry is pushing me to publish this blog post today.</p>
<h2><a name="p-33-setting-my-goals-2" class="anchor" href="#p-33-setting-my-goals-2"></a>Setting my goals</h2>
<p>The specific program I’ve been returning to time and again is called <a href="https://stronglifts.com/">StrongLifts 5x5</a>. StrongLifts is a program that focuses on doing 5 sets of 5 reps of 5 core lifts: squat, bench press, barbell row, overhead press, and deadlift. By the time I was ready to set my super serious goals I had already spent about 6 sessions reacclimatizing to the gym, just letting my muscles shake off their rust and ice, and I had started to find where my weights for each of the StrongLifts lifts were:</p>
<ul>
<li>Squat: 160 lb</li>
<li>Bench press: 100 lb</li>
<li>Barbell row: 100 lb</li>
<li>Overhead Press: 85 lb</li>
<li>Deadlift: 145 lb</li>
</ul>
<p><em>Impressively</em> low! Regardless, as I was looking ahead at a strict 3x a week schedule where I’d try to make as much progress as I could I actually became concerned about the balance of strength across my body. I knew I hadn’t lifted before this in a long while, so I wondered if it was possible that some core muscle group got disproportionally weaker compared to the rest of my muscle groups? Put another way: if I was stronger in, say, squats and moving higher weight there, but I was comparatively weak in, say, deadlifts, could that lead to injury?</p>
<p>So I did some research on what the right balance between these lifts might be. It didn’t seem like this was a thing that was talked a lot about in lifting communities, but I was able to cobble together some sources and average out some percentages to get the following set of proportions comparing each lift to squat.</p>
<ul>
<li>Deadlift should be <strong>115%</strong> of squat.</li>
<li>Overhead press should be <strong>45%</strong> of squat.</li>
<li>Bench press should be <strong>70%</strong> of squat.</li>
<li>Barbell row should be <strong>70%</strong> of squat (should be about the same as bench).</li>
</ul>
<p>I was satisfied enough with these numbers (though who knows how they hold up with respect to different body weights, body types, or at higher weights), so I started using them to inform goal weights. I wanted to set a squat goal at 185 lbs, since that’s a nice clean 45 lb plate + 25 lb on each side, and then set the goal weights for the other lifts using the ideal proportions. This gave me the following values for my first milestone:</p>
<ul>
<li>Squat: <strong>185 lb</strong>.</li>
<li>Deadlift: 115% of 185 lbs is 212 lb, let’s call that <strong>215 lb</strong> (increments of 5 lb are standard)</li>
<li>Overhead press: 45% of 185 lbs is 83 lb, let’s call that <strong>85 lb</strong>.</li>
<li>Bench press: 70% of 185 lbs is 129.5 lb, let’s call that <strong>130 lb</strong>.</li>
<li>Barbell row: same as bench, <strong>130 lb</strong>.</li>
</ul>
<p>My plan was to achieve those weights on each lift, and wait to progress any further on any lift until every lift had hit their target. This gave me confidence that I would progress in a balanced, safe way.</p>
<p>Another piece of arithmetic I was curious to perform was how long this would take me. In StrongLifts 5x5, if you don’t fail at a weight, you move up, otherwise you try again next time. You move up 5 lb for all lifts except for deadlift, where you move up 10 lb. You squat every session, and you do each of the others only every other session. So to move up 20 lb in squat, you need to have 4 sessions without failure. To move up 20 lb in bench press, you need 4 bench press sessions without failure, which is 8 overall sessions since you only do bench every other session. To move up 20 lb in deadlift, you need 2 deadlift sessions, which is 4 overall.</p>
<p>So at my numbers, that looks like:</p>
<ul>
<li>Squat: 185 lb goal weight minus 160 lb current weight is 25 lb to go, 25 lb divided by 5 lb per session without failure is 5 sessions to go.</li>
<li>Deadlift: 185 lb - 145 lb = 40 lb to go, meaning 4 deadlift sessions at 10 lb each, meaning 8 overall sessions.</li>
<li>Overhead press: 85 lb - 85 lb = 0, already there.</li>
<li>Bench press: 130 lb - 100 lb, 6 bench sessions, 12 overall.</li>
<li>Barbell row: same as bench, 6 / 12.</li>
</ul>
<p>After that milestone, I set another one with squat at 225 lb (a nice clean couple of 45 lb plates on either side) and did the same math out at the value. Everything calculated out, here’s a summary of all the information I was interested in:</p>
<div class="md-table">
<table>
<thead>
<tr>
<th>Lift</th>
<th>Current</th>
<th>Ideal % of Squat</th>
<th>Milestone 1</th>
<th>M1 sessions to go</th>
<th>Milestone 2</th>
<th>M2 sessions to go (after M1)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Squat</td>
<td>160 lb</td>
<td>100%</td>
<td>185 lb</td>
<td>5 sessions</td>
<td>225 lb</td>
<td>8 sessions</td>
</tr>
<tr>
<td>Deadlift</td>
<td>145 lb</td>
<td>115%</td>
<td>215 lb</td>
<td>7x2=14 sessions</td>
<td>255 lb</td>
<td>4x2=8 sessions</td>
</tr>
<tr>
<td>Overhead Press</td>
<td>85 lb</td>
<td>45%</td>
<td>85 lb</td>
<td>0 sessions</td>
<td>100 lb</td>
<td>3x2=6 sessions</td>
</tr>
<tr>
<td>Bench Press</td>
<td>100 lb</td>
<td>70%</td>
<td>130 lb</td>
<td>6x2=12 sessions</td>
<td>155 lb</td>
<td>5x2=10 sessions</td>
</tr>
<tr>
<td>Barbell Row</td>
<td>100 lb</td>
<td>70%</td>
<td>130 lb</td>
<td>6x2=12 sessions</td>
<td>155 lb</td>
<td>5x2=10 sessions</td>
</tr>
</tbody>
</table>
</div><p>As I was in the gym lifting, everytime I looked at the StrongLifts 5x5 app to update values, I found myself going back into my note-taking app and looking at these values. I knew this was information I really valued because I had to dig to get it every time. I cared a lot about my progress to the goal, as well as how my current balance is. I wanted to see where I was contextualized in these ways.</p>
<p>At this point, my developer senses started going off: if something’s complex enough to be a useful Excel spreadsheet, it might be a good candidate for an app.</p>
<p>And an app can bring something more to the table, over just the raw calculations. I caught myself visualizing in my imagination something very like the tick mark line visualization I put in the prototype.</p>
<p>So, being a developer, I prototyped a thing that I hoped could <em>show</em> people without having to <em>tell</em> them what I was envisioning.</p>
<h2><a name="p-33-the-app-3" class="anchor" href="#p-33-the-app-3"></a>The app</h2>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/2089072ae9a1df5c8c4d1420bf205f9760618272.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/2089072ae9a1df5c8c4d1420bf205f9760618272" title="lifting-goals-main-page-screenshot"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/2089072ae9a1df5c8c4d1420bf205f9760618272_2_265x500.png" alt="lifting-goals-main-page-screenshot" data-base62-sha1="4DORkyI7N1lbLoyXwUsiQBGatua" width="265" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/2089072ae9a1df5c8c4d1420bf205f9760618272_2_265x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/2089072ae9a1df5c8c4d1420bf205f9760618272_2_397x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/2089072ae9a1df5c8c4d1420bf205f9760618272_2_530x1000.png 2x" data-dominant-color="F0EAEA"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">lifting-goals-main-page-screenshot</span><span class="informations">1080×2036 314 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Before getting into it I want to say, and perhaps this is obvious – I’m not a vis guy. I don’t find myself having instincts or insights into how to make a thing look pretty. But I do find myself having instinctual thoughts on what the user will want to know and want to do, and how they might want to do it. Whether they’re good thoughts or not, who knows, but at least they come to me, and I had a few of them for this app.</p>
<p>The screenshot shows the following on the main app page:</p>
<ul>
<li>At the top, for each lift you see where you currently lie in relation to your goals, denoted by the colored zones. In the screenshot, along the Squat line the 190 shows where you’ll hit your “intermediate” goal, which is the zone you’ll be in until you hit your “advanced” goal at 265.</li>
<li>Below those tick mark lines is a legend showing the colors assigned to each of your goals.</li>
<li>The thick red arrows below the legend show the distance to go, both in weight and sessions (without fail), until each lift hits the next goal.</li>
<li>The double bar graph shows the proportions comparing your lift weights to your bodyweight (left), and comparing your other lifts to squat (right), giving you that information about the “balance” between your muscle groups that I had originally sought out.</li>
<li>At the bottom, the red circles are inputs where you can update your current values.</li>
</ul>
<p>The biggest thing I was going for here is the at-a-glance dashboard of it all. You’re seeing the “whole picture”, which is important for that visual contextualization I was seeking. For example, my ambition with the tick mark lines at the top was to give the user a spatial sense, more of a feeling than anything, of where their current weights lie with respect to their goals. There’s the part of your brain that can look at the raw number of “25 lb to go til I hit the ‘advanced’ goal” and understand it mathematically, and then, I believe, there’s a totally different part of your brain, perhaps the part more to do with spatial reasoning, that can <em>feel</em> that information in a totally different way by being able to <em>see</em> it. The existence of pie charts may support this neurological hypothesis.</p>
<p>Another small instinct I followed was about how to make the interaction cost for setting your current values as minimal as possible. Those inputs can be selected and typed into, like any other input. But for whatever reason even that felt like a lot to ask the user to do for my lil’ old app here. So you can also update those values by clicking / pressing and dragging – as you drag to the right or up the value goes up, and as you drag to the left or down the value goes down. I figured this way you don’t even have to change the way you hold your phone, you’re just using your thumb and you never have to type anything.</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/86146403d402f461cf34371f1b85d02e249993db.gif" alt="lifting-goals-interaction" data-base62-sha1="j87Fpk0k0bzEhOSy8IOj4Bc6FAf" width="277" height="500" class="animated"></p>
<h3><a name="p-33-setting-your-own-goals-4" class="anchor" href="#p-33-setting-your-own-goals-4"></a>Setting your own goals</h3>
<p>The other big interaction is in the settings modal, which you can access with the settings wheel button on the bottom.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34" title="lifting-goals-settings-modal-screenshot"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34_2_263x500.png" alt="lifting-goals-settings-modal-screenshot" data-base62-sha1="tJ3OFyneHK367aAO4F94lr7TqxS" width="263" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34_2_263x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34_2_394x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/d0570cc1e5a385b010dc7bb389ea0ea10e1dfd34_2_526x1000.png 2x" data-dominant-color="D6D1D0"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">lifting-goals-settings-modal-screenshot</span><span class="informations">1080×2046 273 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Here, you set your own goals – their names and their weight values for each lift. It shows you the ratio bar graph display for your goal weight values themselves, so you can see what the weight proportions of your actual goal are. You can also set goals as ratios of your bodyweight, that would change if your bodyweight changes, in case you had a goal of, say, bench pressing your bodyweight.</p>
<h2><a name="p-33-the-tech-5" class="anchor" href="#p-33-the-tech-5"></a>The tech</h2>
<p>Check out the repo: <a href="https://github.com/samgqroberts/lifting-goals" class="inline-onebox">GitHub - samgqroberts/lifting-goals: An app for setting and tracking progress toward goals in the 5x5 lifting program.</a></p>
<p>I chose not to stray from my expertise at all for this in order to focus as much as possible on just the product level prototyping. So I chose React, my bread and butter.</p>
<p>Otherwise, I have almost zero dependencies. No Material UI, no third-party CSS-in-JS solutions. This was actually the first time I went gung-ho on inline styles in React. And you know what? It pretty much worked. The biggest issue is that you can’t define CSS pseudo-classes in inline React styles, so I had to escape-hatch out to a CSS file <a href="https://github.com/samgqroberts/lifting-goals/commit/6722d2b28951f6c6a0d5bc898596ee9967dcf8d7#diff-85da366246340e911d7a0eb9153e64137a1b31f0686c74d9aef9ae4f032cb09eR24">to hide the spinner buttons</a> on the numeric inputs. The other issue is that the lack of classnames makes working with the Chrome inspector a little tougher. There are no classnames to give semantic labels to the HTML elements, and you can’t update CSS in the inspector and apply it to all instances of the component. But you know what? With hot reloading not even requiring a page refresh these days, the second thing there is much less of an issue. You can just change the CSS values in your code editor and see it propagated to all instances of that component immediately, just as if you were editing in the inspector. All that said, it’s almost certainly a better idea to go with a bonafide CSS in JS solution like <a href="https://styled-components.com/">Styled Components</a>. I was just trying to keep things lightweight and try out this new strategy.</p>
<p>As far as writing your CSS in JS vs. in CSS files – part of the reason I like React so much is how it consolidates HTML and JS into one <em>uniform, well-typed</em> place. I’ve had to work with Angular over the past couple years and I miss this consolidation so much. Angular has its HTML in these template files that get type checked, mostly, but intellisense has no idea what to do with them, and the “find usages” function in your IDE won’t know how to look in them. And you’re separating out two things that, in my opinion, shouldn’t be separated out. The traditional logic that says they deal with different things, the layout versus the behavior, and therefore there should be some separation holds exactly zero water with me. The harmony of having HTML and JS together in React disproves that wholly to me. You should separate out things that need to be used in multiple contexts, sure, but that’s not what Angular templates are doing.</p>
<p>Anyway… that’s how I feel about CSS too. Give me everything that defines the thing, the Component, in the same place, and let it be as well-typed as it can possibly be, which CSS in JS can do.</p>
<p>The only runtime dependency I ended up adding was <a href="https://docs.superstructjs.org/">superstructjs</a>, a project I’m a big fan of. It offers a DSL that allows you to define data types, then use those types to validate data. The best thing about it, though, is it works so well with TypeScript. You don’t have to manually redefine a TypeScript type for some data type you defined via superstruct – you can automatically derive it. I use this library to perform well-typed ser/de on the user data JSON the app stores in localstorage.</p>
<p>The only dev dependencies I added were <a href="https://github.com/samgqroberts/lifting-goals/commit/1408729b44b17f73265138d847b9770201aaee37">to set up my eslint / prettier environment</a>. I have become dependent on never manually fixing code style. The first thing I do on any new project, regardless of language, is set up linting / code style rules (I tend to go with as out-of-the-box / standardized a setup as possible, whatever seems to be the community’s preferences) and set up my VSCode to automatically fix all linting errors on save. This is one place where the machine can and should just do the thing for me.</p>
<p>I didn’t add tests because the cost / benefit of UI testing, which is already skewed in general, is even more skewed in a prototyping context. There needs to be a balance between how much investment we make in any particular module of code vs. how certain we are the module is right, has legs, etc. To borrow a concept from <a href="https://theleanstartup.com/">The Lean Startup</a>, which parts of your code correspond to functionality that you have validated learning to support? As this is a prototype, we have almost no validated learning, no certainty in the appropriateness of any feature, and therefore not much confidence in the future of any particular code module. So the benefit in the automated testing cost / benefit curve is fairly low for any bit of code – automatically checking that our code has no changes has less value if the likelihood that we’ll want to change it soon is fairly high. And, as I mentioned, the cost of UI testing (and UI test <em>maintenance</em>) is fairly high, at least with my skills now. And it reaches way higher if you want to cover all aspects of the user interface, including styling, which people usually don’t even bother with, so the cost / benefit curve takes another hit there. All this is to say: no tests for this UI-only prototype.</p>
<p>I feel the need to mention – I love testing. I love Test-Driven Development, I love refactoring under good test coverage, I love investing in a good test harness. If the cost of UI test writing and maintenance was much lower, then I’d definitely have tests alongside even the most uncertain prototype. If the prototype was, say, a Python Flask HTTP server, with its well-defined and easily-programmatically-accessed user interface, then I’d already have decent test coverage for all of the API endpoints even at this stage of development. For that particular cost / benefit curve, writing tests <em>increases</em> my development velocity, even for short-term, scrappy, uncertain stuff, by enabling TDD and letting me <em>see</em> the behavior more clearly. I wish I could have that for frontend development, but I’ve never achieved it.</p>
<h2><a name="p-33-reflections-6" class="anchor" href="#p-33-reflections-6"></a>Reflections</h2>
<blockquote>
<p>There’s really no good ideas that aren’t ideas that someone saw through to the end… nothing just is so good that it kind of moves itself. A person pushed that boulder up that hill… and you only in getting to the end realize it was a good idea – at the end of it. I’ve had this with scripts – the scripts that end up really great and scripts that end up horrible were equally as hard to make… you just have to keep pushing through to the end to see whether it’s gonna work or not.</p>
<p>– Megan Gans</p>
</blockquote>
<p>My traditional creative pattern that’s probably all too familiar to a lot of people has been this: have an idea for a project, get excited about it, start it, then hit the double wall of all the unglamorous work that goes into it plus the creeping doubt that the effort will be worth it. Ultimately I often walk away before any satisfying conclusion.</p>
<p>The unglamorous work is what it is – there will always be a significant amount of unglamorous work along the path of any creative endeavor. That can usually be worked through with discipline and keeping the value of the end goal in mind. But when you start doubting the value, that’s when you end up losing your motivation. I’ve walked away from a lot of projects I was initially excited about because of that doubt.</p>
<p>So when I heard Megan Gans say that quote <a href="https://www.youtube.com/watch?v=pUJL-q7xZcU&amp;t=2804s">at this point</a> in episode 64 of <a href="https://thealwayssunnypod.com/">The Always Sunny Podcast</a>, I felt like she was talking to me. The idea that you really don’t know whether an idea is good or not until you hit some critical point toward the end of its lifecycle makes a lot of sense to me, intuitively, and the fact that it’s coming from someone as successful in creative endeavors as Megan Gans is very convincing. It directly addresses my biggest de-motivator, that creeping doubt.</p>
<p>I listened to that episode about a third of the way into pursuing this app idea, and it just so happened I was right in the middle of experiencing the doubt. So I decided to try to take the words to heart and finish the darn thing. Get it out the door, write a blog post about it, make sure it was in a showable state, make sure the thing I was going for took some kind of visible form. Her words have been in the back of my mind the whole way.</p>
<p>So, now that I’ve done it and can take a look at it – do I think it’s a good idea? <em>Ehhhhh</em>… <img src="https://discourse.samgqroberts.com/images/emoji/google/smile.png?v=12" title=":smile:" class="emoji" alt=":smile:" loading="lazy" width="20" height="20">. It’s got a very limited market / use case. And, really, it should probably just be another page in the <a href="https://stronglifts.com/#app">StrongLifts 5x5 App</a>, if anything.</p>
<p>But you know what? I did the thing, and the project took form. I’m taking away a very positive lesson in seeing things through that I hope becomes a standard.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/the-atoms-of-obsidian-md</guid>
      <title>The Atoms of Obsidian.md</title>
      <description>I’ve been trying out Obsidian.md  and thinking a lot about personal knowledge management systems over the past couple months.</description>
      <link>https://samgqroberts.com/posts/the-atoms-of-obsidian-md</link>
      <pubDate>Thu, 22 Sep 2022 19:49:57 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’ve been trying out <a href="https://obsidian.md/">Obsidian.md</a>  and thinking a lot about personal knowledge management systems over the past couple months.</p>
<h2><a name="p-29-personal-knowledge-management-1" class="anchor" href="#p-29-personal-knowledge-management-1"></a>Personal Knowledge Management</h2>
<p>Put simply, personal knowledge management is your system for note-taking. You could imagine many different ways of doing this. Some people keep a series of notebooks, organized by topic. Some people keep a mish-mosh of Google Docs, scraps of paper, and emails sent to themselves without any discernible organization system – this was me until recently. And some German sociologists keep meticulously annotated and organized slips of paper stored in a series of boxes, but more on that later.</p>
<p>There is a growing movement of people trying to improve PKM capabilities with software. The two that seem to be referenced the most in the space are <a href="https://obsidian.md/">Obsidian.md</a> and <a href="https://roamresearch.com/">Roam Research</a>. Beyond that there’s <a href="https://www.notion.so/">Notion</a>, though it’s more structured and geared toward team collaboration rather than <em>personal</em> knowledge management. And beyond that are the more straightforward note-taking apps, like <a href="https://www.microsoft.com/en-us/microsoft-365/onenote/digital-note-taking-app">OneNote</a>, <a href="https://evernote.com/">EverNote</a>, and <a href="https://www.google.com/keep/">Google Keep</a>, among many others, which can be effective but lack PKM-specific innovations.</p>
<h2><a name="p-29-obsidian-2" class="anchor" href="#p-29-obsidian-2"></a>Obsidian</h2>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/7b519051c404d0e3e9868e7b86a8fbbe164599da.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/7b519051c404d0e3e9868e7b86a8fbbe164599da" title="Pasted image 20220922134941"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b519051c404d0e3e9868e7b86a8fbbe164599da_2_690x446.jpeg" alt="Pasted image 20220922134941" data-base62-sha1="hAVtEVp6vaPOwN67JZyeBZSoohs" width="690" height="446" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b519051c404d0e3e9868e7b86a8fbbe164599da_2_690x446.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b519051c404d0e3e9868e7b86a8fbbe164599da_2_1035x669.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b519051c404d0e3e9868e7b86a8fbbe164599da_2_1380x892.jpeg 2x" data-dominant-color="3B3B3B"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220922134941</span><span class="informations">3824×2474 1.24 MB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Obsidian has a fairly simple primary feature set, shown in this screenshot.</p>
<ul>
<li>On the left panel there is a familiar file tree with folders and notes.
<ul>
<li>All writing goes in files called notes.</li>
</ul>
</li>
<li>In the center there are the note(s) you’re working on.
<ul>
<li>Notes are written in <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a>.</li>
</ul>
</li>
<li>You can create “internal links” from one note to another note by putting the other note’s title in two sets of braces, eg. <code>[[Obsidian]]</code>.
<ul>
<li>These look like regular link text unless your cursor is over it.</li>
</ul>
</li>
<li>On the right panel there is a list of note mentions from these links.</li>
</ul>
<h3><a name="p-29-just-take-notes-3" class="anchor" href="#p-29-just-take-notes-3"></a>Just take notes</h3>
<p>Right away, I’ll say that <strong>Obsidian has a really nice interface for simply writing Markdown</strong>. Obsidian automatically renders the Markdown as you write (eg. beginning a line with <code>#</code> makes the font larger, since that indicates a heading) giving you a seamless and aesthetically pleasing loop of write - read - write again. The dark theme, the use of whitespace, and the fluid Markdown rendering gives me a joy when I write that I haven’t found quite as much in other tools.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44" title="Pasted image 20220922135026"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44_2_690x446.jpeg" alt="Pasted image 20220922135026" data-base62-sha1="ddixgHvabpfBRxxeWdEfGMC4Guo" width="690" height="446" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44_2_690x446.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44_2_1035x669.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/5c9d1c7b8510876a7ccaf0c63fc7aea076a6bf44_2_1380x892.jpeg 2x" data-dominant-color="3A3A3B"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220922135026</span><span class="informations">3824×2474 799 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>The zen of writing in Obsidian is a big draw that’s led me to realize just how important taking notes is, any way you can, whatever your system is.</p>
<p>It shouldn’t surprise me, it’s a similar power to coding. You encode some knowledge then you can build upon it. As Bret Victor says in his essay <a href="http://worrydream.com/LearnableProgramming/">Learnable Programming</a>, the ability to “see the state” and “create by reacting” are essential elements to building – reacting to what’s there facilitates the iteration loop.</p>
<p>With note-taking, what you’re building is a body of information, and writing ideas or observations down helps you write the next idea or observation down or refine that idea. Plus, something unique happens in the simple action of writing something down – you remember it better.</p>
<h3><a name="p-29-networked-knowledge-4" class="anchor" href="#p-29-networked-knowledge-4"></a>Networked knowledge</h3>
<p>As you create notes and link them in Obsidian you’re not just creating a stack of separate pieces of paper but rather a tangible network of knowledge.</p>
<p>This network is shown most clearly in the <strong>graph view</strong> that Obsidian offers.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/9c466ba76a081658b4cbb925237324d189cc65b3.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/9c466ba76a081658b4cbb925237324d189cc65b3" title="Pasted image 20220922121556"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9c466ba76a081658b4cbb925237324d189cc65b3_2_690x446.jpeg" alt="Pasted image 20220922121556" data-base62-sha1="mitmmoEE1v0TvS0ldpqiVpuAi2f" width="690" height="446" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9c466ba76a081658b4cbb925237324d189cc65b3_2_690x446.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9c466ba76a081658b4cbb925237324d189cc65b3_2_1035x669.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9c466ba76a081658b4cbb925237324d189cc65b3_2_1380x892.jpeg 2x" data-dominant-color="3C3B3B"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220922121556</span><span class="informations">3824×2474 655 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Each node represents a note, and each line shows that those notes are connected via at least one internal link (those links with the double square brackets, eg. <code>[[Obsidian]]</code>). With this feature, you can visually explored your idea graph, and perhaps see connections in these paths you hadn’t considered before.</p>
<p>The interface lets you play with how this graph gets visualized. Here’s a zoomed-in snapshot of the nodes surrounding the <code>Obsidian</code>, in one particular configuration.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/64e0bb756364957278417ccefc2a4caa3dc01f13.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/64e0bb756364957278417ccefc2a4caa3dc01f13" title="Pasted image 20220921144159"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/64e0bb756364957278417ccefc2a4caa3dc01f13_2_690x429.jpeg" alt="Pasted image 20220921144159" data-base62-sha1="eopeujaAT6nUYIaXf0JkThgsDIf" width="690" height="429" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/64e0bb756364957278417ccefc2a4caa3dc01f13_2_690x429.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/64e0bb756364957278417ccefc2a4caa3dc01f13_2_1035x643.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/64e0bb756364957278417ccefc2a4caa3dc01f13_2_1380x858.jpeg 2x" data-dominant-color="413F40"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220921144159</span><span class="informations">3354×2088 724 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>It’s interesting what ends up next to each other. All these nodes are related via some path through my personal knowledge graph. Their proximity means <em>something</em>.</p>
<p>You can also see a more focused view of the context any note exists in via a “local” graph view, like this one for my Obsidian note.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/7b83a0f36877798c90f13cdc9be509b90c3db35d.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/7b83a0f36877798c90f13cdc9be509b90c3db35d" title="Pasted image 20220910135628"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b83a0f36877798c90f13cdc9be509b90c3db35d_2_690x446.jpeg" alt="Pasted image 20220910135628" data-base62-sha1="hCEK0zxdypfIFi39biXkPj8N73L" width="690" height="446" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b83a0f36877798c90f13cdc9be509b90c3db35d_2_690x446.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b83a0f36877798c90f13cdc9be509b90c3db35d_2_1035x669.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/7b83a0f36877798c90f13cdc9be509b90c3db35d_2_1380x892.jpeg 2x" data-dominant-color="373737"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220910135628</span><span class="informations">3824×2474 364 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>The fact that every time you add a thought to Obsidian you’re building out this graph has proven to be very addictive for me. I’m <em>building</em> something important, personal, perhaps with emergent properties. As a person who likes building things, this motivates me to jump back in and get thoughts down. And given my earlier realization that taking notes in any way is a majority of the battle, this motivation is well-appreciated.</p>
<p>This context is also helpful when authoring a note. It’s another interesting way to create by reacting. You’re writing in your note on <code>Obsidian</code> and you see that nearby on the graph view is the note for <code>Functional Programming</code>. Perhaps you could synthesize some new connection from this, further building your body of knowledge.</p>
<p>A note’s immediate context is shown to you in another way within the note view, so you don’t have to rely on the graph view for all your contextualization. On the right side panel you can see all the notes that have linked mentions to the note you’re authoring, plus a little snippet of the text surrounding the mention.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/fadd720866d1551afef57a9a68f4ed362d2e4997.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/fadd720866d1551afef57a9a68f4ed362d2e4997" title="Pasted image 20220922134513"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fadd720866d1551afef57a9a68f4ed362d2e4997_2_139x500.jpeg" alt="Pasted image 20220922134513" data-base62-sha1="zNfOmpgGOXEmgbcgyCaZjKhnFpd" width="139" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fadd720866d1551afef57a9a68f4ed362d2e4997_2_139x500.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/fadd720866d1551afef57a9a68f4ed362d2e4997_2_208x750.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/fadd720866d1551afef57a9a68f4ed362d2e4997_2_278x1000.jpeg 2x" data-dominant-color="222221"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220922134513</span><span class="informations">602×2164 93.3 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Perhaps as I’m writing in my <code>Obsidian</code> note I glance at these mentions and see a snippet from my daily note from 5 days ago that says:</p>
<blockquote>
<p>… just finding it an absolute joy to write in [[Obsidian]], it really keeps me coming …</p>
</blockquote>
<p>And I’m thereby inspired to further explore the joy and zen of writing in Obsidian, which turns into a section in this blog post.</p>
<h3><a name="p-29-daily-notes-5" class="anchor" href="#p-29-daily-notes-5"></a>Daily notes</h3>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/fa6493427b2d5e78a262e501d0c23eb80f3df8b7.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/fa6493427b2d5e78a262e501d0c23eb80f3df8b7" title="Pasted image 20220910140235"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fa6493427b2d5e78a262e501d0c23eb80f3df8b7_2_340x500.png" alt="Pasted image 20220910140235" data-base62-sha1="zJ4QDLI0vyEdosEy8JAJYesIKAD" width="340" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fa6493427b2d5e78a262e501d0c23eb80f3df8b7_2_340x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/fa6493427b2d5e78a262e501d0c23eb80f3df8b7_2_510x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/original/1X/fa6493427b2d5e78a262e501d0c23eb80f3df8b7.png 2x" data-dominant-color="202020"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Pasted image 20220910140235</span><span class="informations">544×798 24 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Obsidian has a built-in “daily notes” concept. There’s a straightforward button on the left panel that opens (creating if necessary) a new note with the current date as the title (using <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format, thank goodness).</p>
<p>I <em>really</em> like this feature. Most of my writing goes in these daily notes. I’ve found that I really want to capture thoughts in the moment as they come with as little friction as possible, and being only a button click (or configured hotkey) away from being in a note and ready to write something down has meant I end up doing it more. If you look back at the snapshots of my knowledge graph you’ll see I have a filter that makes all my daily notes appear in red, and if you look really close you can see most edges have a daily note at one end.</p>
<p>One thing this daily note-oriented organization style gives me is a peek into the <strong>chronological context</strong> of my thoughts – I can see <em>when</em> I had a particular thought. I can see that I was thinking about Obsidian a lot over the month of August, 2022. I can see that I was thinking about Obsidian the same day I was thinking about Functional Programming. Perhaps some of this contextualization will lead to those interesting syntheses.</p>
<h3><a name="p-29-dreaming-of-more-6" class="anchor" href="#p-29-dreaming-of-more-6"></a>Dreaming of more</h3>
<p>These features of Obsidian are really cool, but I’m left with a big appetite that isn’t fully satisfied.</p>
<p>Take the example of writing in my “Obsidian” note, and how nice it is that I’m informed by all the mentions it has while I’m writing. The problem with that example is that I <em>don’t</em> write in my “Obsidian” note. I do almost all of my writing in the day’s daily note, and the mentions there aren’t very helpful.</p>
<p>Trying to examine why I like taking notes this way, I’ve come up with this: it’s the closest way to how my brain builds knowledge. I don’t necessarily open some long-running note on some thing, some entity, like “Obsidian”. I have ideas and observations as time goes on, and those new ideas end up enriching my understanding of that entity. Then, really, my knowledge on a subject is encoded solely in the context of ideas and observations I’ve had referring to that subject.</p>
<p>So according to this hypothesis, it’s these ideas and observations that are the most important things for me to capture, and the subjects themselves in a way exist only in relation to them. It’s at this point I discovered Zettelkasten.</p>
<h2><a name="p-29-zettelkasten-7" class="anchor" href="#p-29-zettelkasten-7"></a>Zettelkasten</h2>
<p>The German sociologist I mentioned earlier was <a href="https://en.wikipedia.org/wiki/Niklas_Luhmann">Niklas Luhmann</a>. He was an incredibly prolific scientific writer active in the mid 20th century, and he credited this productivity to his unique note-taking system he called <a href="https://zettelkasten.de/introduction/">Zettelkasten</a>. It involved writing down a thought on a slip of paper (“zettel”) and organizing it into a box (“kasten”). These slips would be given a unique identifier, and could reference other slips by their identifiers. This way, you could organize related thoughts together, and traverse your graph of thoughts by following these references.</p>
<p>The manual effort required to maintain this system was worth it to Luhmann for the benefit of having the ability to traverse related thoughts, and it proved itself in his productivity. He could pull out a note from the box, pull out related notes to that note, and related notes to them, and as easy as that he’d have a critical mass of ideas and observations, ready to be worked into some publishable form. This networking of information is the part of Zettelkasten that seems to have taken hold quite strongly in the PKM-verse.</p>
<p>Another part doesn’t seem to have been embraced so strongly. When Luhmann wrote something on a slip he wrote exactly one thought. This is called the <a href="https://zettelkasten.de/introduction/#a-zettelkasten-is-a-personal-tool-for-thinking-and-writing">Principle of Atomicity</a>: “each Zettel only contains one unit of knowledge and one only.” Which means the network’s nodes are “single units of knowledge”, or perhaps we can call them “atomic thoughts”. It’s a little hard to intuitively determine what exactly the definition of an atomic thought is, but it’s sufficient to me to say that it’s something way less than an entire Wikipedia article on some entity or concept.</p>
<p>There are a lot of articles and discussion out there talking about Zettelkasten in tools like Obsidian and Roam. But looking around at these discussions, I see violations of this principle. I see an embracing of writing longer-form notes on entire subjects and entities.</p>
<p>So I’m left wondering: in its most basic features, does Obsidian encourage you to write and link atomic thoughts? Or is it geared more toward subjects, entities, whose notes may contain many thoughts?</p>
<h2><a name="p-29-rethinking-the-atoms-8" class="anchor" href="#p-29-rethinking-the-atoms-8"></a>Rethinking the atoms</h2>
<p>Writing notes on subjects and entities, and writing longer-form publishable Markdown files like this blog post, is really nice in Obsidian. These end up being the atoms of Obsidian, the basic building blocks of the experience and the nodes in the knowledge network graph.</p>
<p>My thesis here is that these atoms are too big for a personal knowledge management system. I believe to more closely work alongside the brain, and to unlock the power of the knowledge graph, the atoms that the system embraces need to be Zettels, these atomic thoughts of Luhmann’s. This doesn’t have to be at the expense of having larger molecules represented in the system, but they need to come somewhere behind the first-class citizen of the atomic thought.</p>
<p>So what would a system, using Zettels as atoms, offering an interface at that level of modeling that’s <a href="https://share.unison-lang.org/@pchiusano/code/latest/namespaces/public/blog/;/terms/@vqg5baks0qf3c9025202p0o6gkhdle641c9vo5in1dopqvjbefmgknp8192ln0f4q2svgtbdp94v9bh3l6vcbdmf85br0jeers4f5co">as simple as possible without loss of generality</a>, look like? Here’s a compilation of ideas I’ve had.</p>
<h4><a name="p-29-context-should-be-lightweight-fluid-and-flexible-9" class="anchor" href="#p-29-context-should-be-lightweight-fluid-and-flexible-9"></a>Context should be lightweight, fluid, and flexible</h4>
<p>Right now in Obsidian I can open up a new note and simply write a thought, making sure to end it before I end up writing two thoughts. But some aspects of Obsidian add a bit of friction to that.</p>
<p>Where do I put this new note? Should I have to categorize my thought before writing it into some rigid hierarchical context in the file viewer? Should I have to open up my note on Obsidian to write a new section saying “today I had this observation about Obsidian”? What price do I pay for not having categorized it, or having had to categorize it into some rigid context (like as a section in the Obsidian note)?</p>
<p>If I want to capture chronological context, should I have to lump the multiple thoughts I have throughout the day into a single daily note? Again, what price do I pay for this contextualization?</p>
<p>Or should I be able to simply start writing a thought. Put it into the ether, like depositing a thought into Dumbledore’s <a href="https://harrypotter.fandom.com/wiki/Pensieve">Pensieve</a>. The system could capture the one known bit of context for me: the chronological context. All other context is optional and flexibly applied or changed later. You don’t have a growing series of ugly notes named “Untitled 1”, “Untitled 2” clogging up an otherwise well-organized file tree. You don’t have a number of edges in your graph all pointing to “Obsidian”, but really wanting to point to specific ideas and observations you captured within that note. You have a system that embraces atomic thoughts in the form they come to you in.</p>
<p>Having lightweight context reduces the overhead of writing – even writing a title for a thought is some amount of friction, especially before you’ve fully written out the thought. I want to be able to go from having a thought to having recorded it in as little time as possible, and I don’t want to be punished for this.</p>
<h4><a name="p-29-context-should-be-expressive-10" class="anchor" href="#p-29-context-should-be-expressive-10"></a>Context should be expressive</h4>
<p>So now I can write a Zettel with very little overhead, and the context I apply is optional. When I do choose to apply context, what kinds of context should I be able to apply?</p>
<p>Currently in Obsidian you can apply these types of context:</p>
<ul>
<li>Position in the file tree hierarchy</li>
<li>Directional link relationships with other notes</li>
</ul>
<p>Of course, other types of relationships exist between thoughts and entities. Thought X derives from Thought Y. Thought X refines Thought Y. Thought X synthesizes Thoughts Y and Z. Thought X is an observation about Entity Y. Thoughts X, Y, and Z compose Blog Post B.</p>
<p>Right now all of these would simply look like the same untyped links between notes. I’m not convinced an explosion of types here would be worth its weight, but I’m not convinced that reducing everything to the same type is appropriate either. And again, applying any of these should continue to be optional to keep context lightweight and flexible.</p>
<p>What if we reconsider just the relationships gleaned from the file tree. Right now there’s a root folder, and folders can contain both notes and other folders. Notes in the same folder all have an implicit relationship with each other: “sibling”. They also have a child/parent relationship with the containing folder. I’ve found it annoying that I can’t refer to folders themselves in my mentions – when I have a folder that contains all the notes pertaining to a project I really want to link to that folder when I’m mentioning the project elsewhere, but instead I have to create some additional note within the folder that ends up having the same name as the folder. If we made the folders themselves notes, and made the types of relationships notes could have more expressive by including “sibling” and “child/parent” relationships, we could reconstruct the file tree viewer while enriching the knowledge graph with these new types of links.</p>
<h4><a name="p-29-context-should-be-powerful-11" class="anchor" href="#p-29-context-should-be-powerful-11"></a>Context should be powerful</h4>
<p>At this point I’ve got a network of atomic thoughts with rich relationships. I’ve built a big hill of knowledge, and there’s gold in that hill. I need the right tools to mine it.</p>
<p>In Obsidian, these tools are:</p>
<ul>
<li>The file tree view, which shows me the hierarchical context of any note</li>
<li>The graph view, which shows me a collective view of the reference relationships between notes</li>
<li>The linked mentions section of the right sidebar, which shows me a single note’s reference relationships</li>
</ul>
<p>Even with the current contextual expressivity that Obsidian gives you, what other ways could context be shown back to us? What about showing the collection of all notes that are within an edge distance of 2 of the note you’re working on, as opposed to the local graph view that only shows an edge distance of 1? What about, as we mentioned before, also incorporating the hierarchical relationships into the graph view?</p>
<p>I have a very strong hunch that if we applied interesting graph algorithms, like with <a href="https://neo4j.com/docs/graph-data-science/current/algorithms/">Neo4J Graph Data Science</a>-style tooling, to such rich data we’d be able to gain novel insights into our knowledge graph (“mine the gold”). What about something as simple as seeing a ranking of nodes by number of references? What about seeing that ranking within some time period?</p>
<p>This could also lead the way to new, interesting, context-informed experiences. I have this image in my head of a capturing interface sitting on top of all this. It’s just a textbox at the bottom-center of a page. Above it, ordered by chronological context and fading into the top of the window, are the previous Zettels you’ve written. As you write, suggestions for conceptually related thoughts and entities fade in and out of view on the sides. You have the option to swiftly formalize a link between what you’re writing and those other thoughts. When you create a new paragraph, you start a new Zettel and your previous paragraph gets push upward. A “conceptually follows” link is automatically created between your new thought and the thought you just finished, which you could swiftly undo if you’ve moved onto another line of thinking.</p>
<h3><a name="p-29-you-know-maybe-12" class="anchor" href="#p-29-you-know-maybe-12"></a>You know, maybe</h3>
<p>Of course, all of these ideas are easy enough to throw out there. It’s difficult to imagine what the specifics look like, what design choices would need to be made for this to be viable at all. I’m not sure if a system like this would end up being so noisy it’s unreadable, or so expressive it’s unlearnable.</p>
<p>Also, it would be a shame to throw out what Obsidian excels at – writing longer-form markdown files like this blog post. Ideally we’d be able to combine systems, keep everything good from Obsidian while expanding into the smaller atomic modeling. But again, not super clear how to do that.</p>
<p>But it’s all certainly captured my imagination the past couple months. I really feel like there’s gold in the hills, waiting to be mined.</p>
<p>I will say that after researching the PKM space for this post, Roam looks like it’s got valuable stuff for me. It states “As powerful as a graph database” right at the top of its marketing site. Next step is to cough up $15 and try it for a month.</p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/where-state-goes-react-redux</guid>
      <title>Where state goes: React + Redux</title>
      <description>With React and Redux, you should put state in the Redux store and leave everything else outside of it. Let’s explore why.</description>
      <link>https://samgqroberts.com/posts/where-state-goes-react-redux</link>
      <pubDate>Thu, 18 Aug 2022 17:45:38 GMT</pubDate>
      <content:encoded><![CDATA[<p>With React and Redux, you should put state in the Redux store and leave everything else outside of it. Let’s explore why.</p>
<p>If you’re not familiar with React and/or Redux, here’s a summary highlighting the parts of them that are important to this post.</p>
<p><a href="https://reactjs.org/">React</a> is “a Javascript library for building user interfaces.” It offers a <em>declarative</em> interface for the programmer to say what should be on the page. A React “component” is the basic building block of the UI. At its heart a component is just a function that “reacts” to different state configurations by saying what should be displayed given that configuration. Because these functions say what <em>should</em> be displayed (this is what we mean by “declarative”), they can be and often are run multiple times by the overall framework. If the same result is returned, the framework does not make a change to the UI.</p>
<p><a href="https://redux.js.org/">Redux</a>  is “a Predictable State Container for JS Apps”. Redux offers a clean ways to store and manage application state. Redux usage consists of setting up a central Redux “store” where your state goes, defining rules for how that store can change, and connecting that store to anything that may need to use the state. It pairs well with React - Redux manages the state, and React connects to that state and determines what should be displayed.</p>
<h2>
<a name="a-pet-stores-website-1" class="anchor" href="#a-pet-stores-website-1"></a>A pet store’s website</h2>
<p>Our example application will have a central Redux state model, like this:</p>
<pre data-code-wrap="typescript"><code class="lang-nohighlight">interface PetStoreState {
  pets: Pet[],
  theme: 'light' | 'dark'
}

const initialState: PetStoreState = {
  pets: [],
  theme: 'light'
}
</code></pre>
<p>Here our Redux store manages two pieces of information:</p>
<ol>
<li>
<code>pets</code>: A list of pets in the pet store, which we likely got from a fetch to the server.</li>
<li>
<code>theme</code>: A simple user preference for whether to display the page in light or dark mode, which let’s say we got from prompting the user when they first navigated to the page.</li>
</ol>
<p>Let’s say that as part of our render layer we want to display a page header that shows the number of pets in the pet store. We’ll have a React component use a Redux selector to grab the relevant state it needs to display that number, like this:</p>
<pre data-code-wrap="tsx"><code class="lang-nohighlight">const PageHeader = () =&gt; {
  const numPets = useSelector((state) =&gt; state.pets.length);
  return &lt;h1&gt;Browse pets ({numPets})&lt;/h1&gt;;
}
</code></pre>
<p>This React component connects to the Redux store and computes some derived information: the number of elements in the <code>pets</code> array. This computation (accessing the <code>.length</code> field of the <code>state.pets</code> array) is dead simple and extremely fast by design for the sake of this example, but keep in mind that it represents <em>some</em> computation.</p>
<p>As we said before, this React component’s function may be run multiple times in order for the React framework to ensure what’s on screen is up-to-date, but as long as the length of the <code>pets</code> array remains the same the component tells React the same answer for how the UI should look.</p>
<h2>
<a name="where-should-these-pieces-of-information-live-2" class="anchor" href="#where-should-these-pieces-of-information-live-2"></a>Where should these pieces of information live?</h2>
<p>So far we have 3 relevant pieces of information:</p>
<ul>
<li>
<code>pets</code>: The list of pets</li>
<li>
<code>theme</code>: The appearance theme</li>
<li>
<code>numPets</code>: The number of pets</li>
</ul>
<p>We chose to store the first two in Redux and put the last one in a React component to be computed every time the React component renders.</p>
<p>For argument’s sake, here’s an alternative way we could have structured the state and the component:</p>
<pre data-code-wrap="tsx"><code class="lang-nohighlight">interface PetStoreState {
  pets: Pet[],
  numPets: number,
  theme: 'light' | 'dark'
}

const PageHeader = () =&gt; {
  const numPets = useSelector((state) =&gt; state.numPets); // a tiny bit simpler
  return &lt;h1&gt;Browse pets ({numPets})&lt;/h1&gt;;
}
</code></pre>
<p>We’ve taken the last piece of information, the number of pets in the pet store, and moved it from being computed directly in the component where it’s used to being stored in the Redux state.</p>
<p>If we had it this way, the computation that the component does is a little simpler. When the developer is building the component they just have to access a ready-made field in the state rather than know how to / write the computation to determine the length of the array (again, trivial I know, but it’s for the sake of example). And when React is running this function, however many times it will, it will save some (…trivial) amount of compute on each run.</p>
<p>However, our Redux store is now that little bit more complex. Now we have to make sure that the <code>numPets</code> field stays in sync with the <code>pets</code> field whenever the <code>pets</code> field changes. If we had an action in our reducer to add a pet to the store, we’d have to now consider <code>numPets</code>:</p>
<pre data-code-wrap="typescript"><code class="lang-nohighlight">interface AddPetAction {
  type: 'ADD_PET',
  pet: Pet
}

function petStoreReducer(state: PetStoreState, action): PetStoreState {
  ...
  if (action.type === 'ADD_PET') {
    return {
      ...state,
      pets: [...state.pets, action.pet],
      numPets: state.pets.length + 1, // we didn't need this consideration before
    };
  }
  ...
}
</code></pre>
<p>Turns out, this is all a violation of <a href="https://redux.js.org/style-guide/#keep-state-minimal-and-derive-additional-values">Redux’s best practices</a> - Redux tells us to keep the store  as minimal as possible, and put any data that we can derive from what’s in the store outside of the store. Putting the number of pets here complicates the store, where complexity has a higher weight and severity, and leads to more places the developer needs to keep that data in sync with the other data in the store.</p>
<p>So Redux would tell us it’s best to keep <code>numPets</code> being computed on the fly, directly within the component it’s used in, instead of stored in the Redux store.</p>
<h2>
<a name="why-3" class="anchor" href="#why-3"></a>Why?</h2>
<p>We’ve covered that it saves some developer thought (how to compute <code>numPets</code>) when writing the component, and that it saves React some compute when running the component function. But it requires more cognitive load to understand the Redux store, and more manual effort from the developer to keep the store in sync with itself.</p>
<p>This manual effort to keep the store in sync with itself is worse than it looks. Putting <code>numPets</code> in the store actually opens up the possibility for new bugs due to <strong>impossible states</strong>. It is now <em>possible</em> to have the Redux store be in a state where the application has, say, 5 pets in the <code>pets</code> list, but the value 4 in the <code>numPets</code> field. It’s now <em>possible</em> for the store to look like this:</p>
<pre data-code-wrap="ts"><code class="lang-nohighlight">const store: PetStoreState = {
  pets: [pet1, pet2, pet3, pet4, pet5],
  numPets: 4, // impossible state!
  theme: 'light'
}
</code></pre>
<p>The increased manual effort for the developer to keep the store in sync with itself not only means the developer’s life is a little tougher, but also that they could skip this effort:</p>
<pre data-code-wrap="typescript"><code class="lang-nohighlight">function petStoreReducer(state: PetStoreState, action): PetStoreState {
  ...
  if (action.type === 'ADD_PET') {
    return {
      ...state,
      pets: [...state.pets, action.pet]
      // where's numPets?
    };
  }
  ...
}
</code></pre>
<p>Or make a mistake:</p>
<pre data-code-wrap="typescript"><code class="lang-nohighlight">function petStoreReducer(state: PetStoreState, action): PetStoreState {
  ...
  if (action.type === 'ADD_PET') {
    return {
      ...state,
      pets: [...state.pets, action.pet],
      numPets: state.pets.length // whoopsies
    };
  }
  ...
}
</code></pre>
<p>The beauty of keeping the computation in the component is that the <em>machine</em> will <em>automatically</em> keep these pieces of information in sync for us. When the state changes React will re-run the component’s function, which will derive the length of the new <code>pets</code> array. This is done by the machine <em>whenever</em> the state changes, which is exactly when we’d have to consider whether or not to update <code>numPets</code> manually in the stored-in-the-store scenario.</p>
<p>Summarizing what we’ve said, we get a pros/cons table:</p>
<div class="md-table">
<table>
<thead>
<tr>
<th></th>
<th style="text-align:left">Computed in component</th>
<th style="text-align:left">Stored in store</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Pros</strong></td>
<td style="text-align:left"><ul>
<li>No impossible states</li>
<li>Redux store is less complex</li>
</ul></td>
<td style="text-align:left"><ul>
<li>Lower compute cost (avoid component rerender costs)</li>
<li>Easier to write React component</li>
</ul></td>
</tr>
<tr>
<td><strong>Cons</strong></td>
<td style="text-align:left"><ul>
<li>Higher compute cost every time component is rendered</li>
<li>Harder to write React component</li>
</ul></td>
<td style="text-align:left"><ul>
<li>Redux store is more complex</li>
<li>Developer has to address possibility of impossible states</li>
</ul></td>
</tr>
</tbody>
</table>
</div><p>Regarding it being easier / harder to write the component – in both scenarios, some developer needs to know <em>how</em> to perform the computation of getting the length of the array. Only in the stored-in-the-store scenario does the question of <em>where</em> to perform the computation become complicated and potentially error-prone. So that benefit cancels out in the cost to keep the store in sync with itself.</p>
<p>Regarding the compute cost – this computation is extremely fast. We are talking about absolutely negligible amounts of compute. Nobody from the developer to the end user would possibly detect the difference. So we should favor other larger benefits over this.</p>
<p>The overall “why” for the question of which is better here boils down to this: making the developer’s job easier is extremely valuable. Making the component easier to write doesn’t offset the difficulty of keeping the store in sync, and the pros / cons regarding compute costs fall far by the wayside of the importance of making the store more understandable and avoiding impossible states. Higher understandability of the code and fewer impossible states to consider translate into value for the end-user in the form of fewer bugs and faster development. A lot of senior developers spend a lot of time considering understandability of the code and the possibility of impossible states, whether they’re working with React / Redux or not, for exactly this reason.</p>
<h2>
<a name="getting-more-complicated-4" class="anchor" href="#getting-more-complicated-4"></a>Getting more complicated</h2>
<p>This example is certainly contrived in that the tradeoff is very, very obvious - the machine time it takes to compute the number of elements in a list is absurdly low, and the number of times a React component renders doesn’t provide a large enough multiple to that cost for the performance benefit from avoiding those computations to hold much weight.</p>
<p>The temptation might be easier to see if we make the computation more complex. Like what if we had 1 million elements in the <code>pets</code> array, and the page header needed to display the number of pets <em>of a certain breed</em>.</p>
<pre data-code-wrap="tsx"><code class="lang-nohighlight">const PageHeader = () =&gt; {
  const numPoodles = useSelector((state) =&gt;
    state.pets.filter(p =&gt; p.breed === 'poodle').length
  );
  return &lt;h1&gt;Browse poodles ({numPoodles})&lt;/h1&gt;;
}
</code></pre>
<p>Maybe this might start taking some real time to compute, and maybe it might start to look more tempting to store the number of poodles in the Redux store. The answer is <em>still</em> not to confuse your derived state with your essential state. The answer would be to use <a href="https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization">memoized selectors</a>, but I’ll talk about that in another post.</p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/the-whitney-music-box-in-elm</guid>
      <title>The Whitney Music Box, in Elm</title>
      <description>Years ago I discovered Jim Bumgardner’s “Whitney Music Box”. Recently I recreated it in Elm.</description>
      <link>https://samgqroberts.com/posts/the-whitney-music-box-in-elm</link>
      <pubDate>Fri, 17 Sep 2021 14:32:16 GMT</pubDate>
      <content:encoded><![CDATA[<p>Years ago I discovered Jim Bumgardner’s “Whitney Music Box”. Recently I recreated it in Elm.</p>
<p><strong>TL;DR:</strong> <a href="https://whitney.samgqroberts.com">check it out</a>.</p>
<h3>
<a name="math-and-music-1" class="anchor" href="#math-and-music-1"></a>Math and Music</h3>
<p>I’ve always been fascinated by the structure behind music. I’m fascinated with the structure behind any performance or experience, really. Going into college I planned to double-major in math and music, but one fateful night I printed “Hello, World” to the <a href="https://www.bluej.org/">BlueJ</a> console for CS 101 and realized almost immediately that something had to make room.</p>
<p>Most of my exploration into the structure behind music took the form of studying music theory, but there’s so much rigorous math behind sound that continues to capture my attention and imagination. I remember spending a lot of time wondering about the <a href="https://en.wikipedia.org/wiki/Harmonic_series_(music)">overtone series</a>. Why does it all fall into place like that? Why do we experience those particular mechanical waves, arranged in the way they are, in the way that we do?</p>
<p>Really, I mean, what the heck is music?</p>
<h3>
<a name="and-swirly-dots-2" class="anchor" href="#and-swirly-dots-2"></a>and swirly dots</h3>
<p>I don’t remember exactly when, but sometime in college I stumbled upon <a href="http://whitneymusicbox.org/">this link</a> to something called the “Whitney Music Box,” by <a href="https://krazydad.com/about/">Jim Bumgardner</a>. I stared at it for hours, fueled by my existing academic interests (and, perhaps, some collegiate substances). It made me think of the overtone series,  about that mysterious link between mathematical structure and musical experience.</p>
<p>Years later, it popped up on my radar again and rekindled a lot of these college-era thoughts. I decided re-creating it would be a fun, enlightening, and perhaps nostalgic little side project.</p>
<h3>
<a name="and-code-3" class="anchor" href="#and-code-3"></a>and code</h3>
<p>I’ve always found that one of the best ways to understand something, especially the structure behind some experience, is to try to create it myself. <a href="https://whitney.samgqroberts.com">So I did!</a></p>
<p>I decided to build it in Elm because I’m always looking for more concrete Functional Programming experience. If you’re curious, <a href="https://github.com/samgqroberts/whitney-music-box">here’s</a> a link to the repo.</p>
<h3>
<a name="but-mostly-synthesizer-math-4" class="anchor" href="#but-mostly-synthesizer-math-4"></a>but mostly synthesizer math</h3>
<p>The largest technical challenge I encountered was how to programmatically produce a tone with an easily-configurable pitch. It looked like the way to do this was via Javascript’s <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext">AudioContext</a> API. I decided to poke a port through from Elm to Javascript to handle all the AudioContext stuff directly, since A) not having experience with it, I didn’t want to learn how to use it through some indirection via an Elm library, and B) I didn’t find a mature-enough-looking Elm library for it anyway.</p>
<p>Diving into the AudioContext documentation surfaced a deeper technical challenge - I wasn’t familiar with how synthesizers / oscillators worked in general, which is what AudioContext provides an API for (if you want to design the tone programmatically rather than invoke audio files), and which the documentation assumes knowledge of.</p>
<p>I spent a few relatively confusing days looking around for answers. Here is the operant knowledge I consolidated.</p>
<p>We experience sound when mechanical sound waves in the air hit our ear drums. Different instruments produce sound waves with different shapes, which causes them to sound different (“timbre”, pronounced “tamber”). The simplest sound wave might look like the straightforward, smooth sine wave, and this sound wave produces a very pure, synth-y sound. Real-world instruments have all sorts of squiggles in their sound waves, corresponding to louder or softer overtones. An oscillator synthesizer can take the description of a periodic (repeating) wave and produce sound from it. If we give it a wave that looks a lot like, say, the wave a flute makes, the produced sound will sound a lot like a flute (though real-world instruments’ waves constantly change, which means they aren’t exactly periodic, and in general our ear can tell something is synthetic about a purely periodic wave).</p>
<p>So how do we programmatically describe arbitrarily-squiggly waves? The answer lies in the Fourier Theorem, which states that any periodic function (no matter how squiggly) can be approximated by combining some set of sine and cosine waves. This combination of sines and cosines is called a <a href="https://en.wikipedia.org/wiki/Fourier_series">Fourier Series</a>, where we use weights attached to sine and cosine waves of increasing frequencies to specify each component wave that we want to include in the combination. The AudioContext <a href="https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/setPeriodicWave">oscillator API</a> takes in these weights, and produces a tone resulting in the combination of those weights and the corresponding sine and cosine terms.</p>
<p>I wasn’t quite wrapping my head around this until I found an excellent YouTube series about it, specifically <a href="https://www.youtube.com/watch?v=6z3bHpeTQk8&amp;list=PLWMUMyAolbNuWse5uM3HBwkrJEVsWOLd6&amp;index=5">this video</a>  and <a href="https://www.youtube.com/watch?v=n7hvhRdDg5g&amp;list=PLWMUMyAolbNuWse5uM3HBwkrJEVsWOLd6&amp;index=8">the one after that</a>, so if you’re curious about this stuff I’d highly recommend giving these videos a watch.</p>
<p>All of this education resulted in a few key lines of Javascript, which you can see in my codebase <a href="https://github.com/samgqroberts/whitney-music-box/blob/a364ed20e122d0b06ec188f8c66445f83858321b/src/index.js#L52">here</a>. I send a message through a port from Elm to Javascript containing the numeric value of the frequency (pitch) of the tone to play, plus an array of numbers containing the sine term weights that describe the timbre of the tone.</p>
<p>Currently the sine terms are static, set to <code>[0, 0, 1, 0, 1]</code>. The first value isn’t actually part of the Fourier series, it specifies the <a href="https://en.wikipedia.org/wiki/DC_bias">DC offset</a> for the AudioContext API, which we ignore here by setting it to 0. The remaining values specify the amplitude of the fundamental tone and the overtones. So this set of terms specifies a tone with a muted fundamental tone and second overtone, with amplified first and third overtones. The result of this is the timbre of the sound you hear when the dots cross the line.</p>
<p>In the future, I’m considering making those sine terms easily configurable in the UI so people can play around with changing the tone played in the music box. But that might not be this project anymore… that might be a graphical oscillator interface. We’ll see.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/cracking-into-open-source-software</guid>
      <title>Cracking into open source software</title>
      <description>Until last week, I had never made a contribution to an open source project. Especially since this is a first that’s happened almost 10 years into my career as a programmer, it’s gotten me to reflect on how it came together and what skills went into it.</description>
      <link>https://samgqroberts.com/posts/cracking-into-open-source-software</link>
      <pubDate>Wed, 03 Mar 2021 18:05:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>Until last week, I had never made a contribution to an open source project. Especially since this is a first that’s happened almost 10 years into my career as a programmer, it’s gotten me to reflect on how it came together and what skills went into it.</p>
<h3>
<a name="without-any-ado-1" class="anchor" href="#without-any-ado-1"></a>Without any ado</h3>
<p>The <a href="https://github.com/unisonweb/unison/pull/1814">contribution itself</a> was to the Unison codebase manager, disambiguating two particular error cases and providing distinct error messages.</p>
<div align="center">
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/7796575ea2df3fa632e11460e9cc713d03cac068.png" alt="13a1770e56c8bff24079860294e75a1f71f1c275 (2)" data-base62-sha1="h3UVgJeKU8R71uUSYz8X7IAANbq" width="530" height="241"></p>
<p><em>I wrote this error message <img src="https://discourse.samgqroberts.com/images/emoji/google/smiley.png?v=12" title=":smiley:" class="emoji" alt=":smiley:" loading="lazy" width="20" height="20"></em></p>
</div>
<p>For the past few months I’ve had the personal goal to dive into functional programming technologies and communities. Unison is a functional language, heavily derived in design off of Haskell, and written itself in Haskell. Contributing to an alpha functional language by writing in a staple functional language was a perfect exercise for me.</p>
<h2>
<a name="what-went-into-it-2" class="anchor" href="#what-went-into-it-2"></a>What went into it?</h2>
<p>The pull request itself is fairly modest. It adds a couple new data types, changes the signatures of a few functions, provides the copy for a new error message, and ties a bow on it all with a new integration test. But it represents, at least for me, the culmination of a significant amount of time, effort, and education. Here are my reflections on the factors that went into making the contribution.</p>
<h3>
<a name="familiarity-with-the-interface-of-the-project-3" class="anchor" href="#familiarity-with-the-interface-of-the-project-3"></a>Familiarity with the interface of the project</h3>
          <div class="onebox video-onebox">
            <video width="100%" height="100%" controls="">
              <source src="https://i.imgur.com/kZ5CgNA.mp4">
              <a href="https://i.imgur.com/kZ5CgNA.mp4" rel="noopener">https://i.imgur.com/kZ5CgNA.mp4</a>
            </source></video>
          </div>

<p>Unison is a programming language / environment, so it was pretty important that I had spent a lot of time playing around with it. I had built a <a href="https://github.com/samgqroberts/2048-unison">small 2048 clone</a>, which I talk about in <a href="https://samgqroberts.com/posts/learning-by-playing">this other post</a>.</p>
<p>In building that, plus messing around with a few other projects, I had expectedly come across some of the rougher edges of Unison (it is in alpha, after all). The first significant step on the way to becoming a contributor, for me, was creating a few issues on their Github page (<a href="https://github.com/unisonweb/unison/issues/1698">one</a>, <a href="https://github.com/unisonweb/unison/issues/1731">two</a>, <a href="https://github.com/unisonweb/unison/issues/1740">three</a>, <a href="https://github.com/unisonweb/unison/issues/1744">four</a>, <a href="https://github.com/unisonweb/unison/issues/1745">five</a>). The biggest challenge I had to get over here was my inherent cautiousness about bothering the maintainers. I really wanted to do my due diligence so as not to add noise or burden to their lives. Filing these issues and getting welcoming responses got me over that smaller bump, and further led me to think: if I could confidently point out flaws, what’s to say I couldn’t reasonably make improvements from a design perspective?</p>
<p>Also, importantly, I had spent a good amount of time in their Slack channel. Seeing people’s questions and comparisons to other projects over the past few months was helpful to understanding the project. I had also spent a decent amount of time discussing it with friends and making similar comparisons, so I felt confident I there wasn’t some significant gap in my grasp of the surface area of the project’s interface.</p>
<h3>
<a name="familiarity-with-the-implementation-of-the-project-4" class="anchor" href="#familiarity-with-the-implementation-of-the-project-4"></a>Familiarity with the implementation of the project</h3>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/b2931a69a3ee2827b47ed155aafe562d8b87097e" title=""><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e_2_386x500.png" alt="" data-base62-sha1="ptK9ISNHqhdcwGdQcKG6VcgISdw" width="386" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e_2_386x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e_2_579x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e_2_772x1000.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b2931a69a3ee2827b47ed155aafe562d8b87097e_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename"></span><span class="informations">1530×1980 214 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div><br>
Unison is written in Haskell, and, going along with my goal to get more familiar with functional programming, I’m currently about 80 hours into <a href="https://haskellbook.com/">Haskell Programming from first principles</a>. Really big shoutout to that book - taking my time working through it has been very rewarding for me, and was essential for this contribution.</p>
<p>I’ve certainly found that a lot of my object-oriented experience does translate - fundamentally they’re both ways of making the computer do things. Some skills and knowledge pretty directly transfer, like abstract visualization skills, reasoning about data structures, and working with static types, and some skills and knowledge are helpful as reference points to contrast against, like considering the differences between Java’s inheritance and Haskell’s typeclasses. However, there’s a lot of new territory in functional programming, both from a technological and cultural standpoint. I’m finding it’s taking a lot of sleeves-rolled-up time to gain a working familiarity with it, which this book emphasizes and, so far, delivers.</p>
<p>Fortunately the book also introduces the reader to standard Haskell build processes (largely via <a href="https://docs.haskellstack.org/en/stable/README/">Stack</a>), and that helped me navigate Unison’s employment of Stack to build changes, run tests, and run my development version of the executable. The Unison maintainers have also provided a file called <a href="https://github.com/unisonweb/unison/blob/release/M1m/development.markdown">development.markdown</a> which was hugely helpful for bringing me into the codebase.</p>
<p>That all being said, I wasn’t very familiar with a lot that’s going on in the codebase. Not only was I relatively new to Haskell, but it also took a while of staring at different files and pondering the directory structure to get a working handle on the internal architecture. I’d say my contribution was just about at the extent of my capabilities given my relative unfamiliarity.</p>
<p>One theme of confusion I had was around wondering what’s idiomatic in the FP culture, in Haskell, and in the Unison codebase. Should my new data type have been a 3-element sum type or represented as an Either with a new 2-element sum type for the Left case? Where do I even put new data type declarations? I fought through the discouragement of not knowing those answers and hoped that it wouldn’t be too bothersome to the maintainers for me to put up a solid guess and for them to give some feedback. Happily, it seems like I wasn’t too far off with the guess I put up.</p>
<h3>
<a name="general-software-development-experience-5" class="anchor" href="#general-software-development-experience-5"></a>General software development experience</h3>
<p>I’m not sure I would have had the ability or, as importantly, confidence to make this contribution without already having a good number of years’ worth of general experience programming, debugging, communicating about code, and collaborating on a large-scale project via Github.</p>
<div align="center">
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/9e7728cf723af3baba3108966efddec60960743f.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/9e7728cf723af3baba3108966efddec60960743f" title="image"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9e7728cf723af3baba3108966efddec60960743f_2_690x387.png" alt="image" data-base62-sha1="mBQJLDXFeNR7Q9b23vlQgtJi5LF" width="690" height="387" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9e7728cf723af3baba3108966efddec60960743f_2_690x387.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9e7728cf723af3baba3108966efddec60960743f_2_1035x580.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/9e7728cf723af3baba3108966efddec60960743f_2_1380x774.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/9e7728cf723af3baba3108966efddec60960743f_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">1892×1062 186 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p><em>I’ve spent a lot of time around here</em></p>
</div>
<p>I felt very comfortable identifying and emulating their collaboration workflow on Github. I wanted to be very cautious about bothering the maintainers (which was the hill to climb in even putting up the issues I mentioned), so I first created a fork and put up a <a href="https://github.com/samgqroberts/unison/pull/1">development PR</a> that I messed around with over there. Nobody else ever saw that PR, but it was a good way for me to iterate on the PR, including the description / comments. I put a lot of importance in reading my own work - reviewing the PR diff and description like I didn’t write it. If I missed dumb stuff, or something was confusing, that would take up the maintainers’ time, and I wanted to minimize the cost impact as much as possible.</p>
<p>Another challenge my experience helped me with - I actually ran into another bug, believe it or not, while working through this contribution. As you might expect this was super confusing. By this point I had looked through the codebase and poked around at making changes for a few hours, and in every case something unexpected happened the most productive bet to make was that I had done something wrong, or misunderstood something. A lot of general experience debugging helped here - what do we <em>know</em> we know? Perhaps we should test with a value we can safely assume would work? Perhaps we should check out the main branch and test there? Perhaps we should see if other people have run into this? Thankfully this last thought led me to an <a href="https://github.com/unisonweb/unison/issues/1800">open issue for the bug</a>, confirming that I wasn’t crazy.</p>
<h3>
<a name="personal-touches-6" class="anchor" href="#personal-touches-6"></a>Personal touches</h3>
<p>As always, the importance of good people and interactions in software development can’t be understated. And, as usual, the human factor has a profound impact on productivity, motivation and goodwill.</p>
<p>I’m very appreciative that I have friends who are not only passionate and knowledgeable about software, but very generous with their time. Specific shoutout to my friend <a href="https://github.com/seagreen">Ian Jeffries</a> who has been a singularly helpful mentor for me as I’ve taken my first steps into Haskell. A few essential answers from Ian about the Unison codebase architecture and build processes did wonders to release the pressure my head’s contact with the wall was making.</p>
<p>This particular project also benefits from having very inclusive and helpful maintainers. As is evident from their interactions on Slack and Github they really take their time to engage with the community, answer questions, and encourage people to get involved. I was fortunate enough to be able to go through the PR on a video call, even, with one of the cofounders <a href="https://github.com/aryairani">Arya Irani</a>, a cost-impact I never would have expected from him. I found that the personal touch gave a huge boost to my engagement, motivation and goodwill for future contributions.</p>
<h3>
<a name="time-energy-7" class="anchor" href="#time-energy-7"></a>Time &amp; Energy</h3>
<p>Another important factor here is that I’m fortunate (and so grateful) to be in a period in my life where I’m happily working part-time. I have a lot of free time and energy to improve my skills and build things, which isn’t a luxury most people have at any given period in their life. I certainly can’t stay in this period indefinitely, which is why I’m very motivated to make the most of it.</p>
<p>This is to say that anything worth doing takes both time and energy, which are often the scarcest resources we have. In this case I didn’t have to stay up nights and weekends or dig deep for the energy to chase my personal goals. I can fairly confidently say that if I had a full-time job that I was pouring myself into, it would have been unlikely that this contribution came together.</p>
<h2>
<a name="feelin-good-8" class="anchor" href="#feelin-good-8"></a>Feelin’ good</h2>
<p>On top of all these building blocks, I feel very fulfilled in my first OSS contribution. The PR was merged on that video call, and the four of us present in the meetup cracked open beer / kombucha to celebrate the occasion. It was a very nice moment. Again, the personal touches generate motivation to continue contributing.</p>
<p>This post isn’t all so much meant to simply celebrate a proud first for me. It’s partly to acknowledge and appreciate the privilege I enjoy. It’s partly to provide a personal story that could help demystify something that might seem opaque to people. And it’s partly to reflect on that piece of advice I hear around for people learning programming: a great way to learn and grow is to get involved with and contribute to an open source project.</p>
<p>Breaking down everything that went into this contribution has made me question that piece of advice. Take away even one of the factors I mentioned and making the contribution would have been a lot harder (surely much more frustrating). Unison is probably one of the more complex OSS projects I could have chosen for my first contribution, sure, but I still believe I’d have to rely on a lot of the same building blocks going into a simpler project.</p>
<h3>
<a name="the-importance-of-mentorship-9" class="anchor" href="#the-importance-of-mentorship-9"></a>The importance of mentorship</h3>
<p>I’d like to propose an alternative to (that partly agrees with) that piece of advice: seek mentorship, wherever you can find it. Find people with more experience, skills, knowledge and perspective than you and try to learn as much as you can from them. Mentorship is probably the best way to overcome nearly every challenge I’ve mentioned here. If you can soak up other people’s familiarities with some project’s interface, implementation, and development processes, you’ll be able to skip a lot of the discouraging (and sometimes defeating) trials.</p>
<p>Mentorship is especially helpful with respect to an especially discouraging problem one has to deal with when diving into something new: unknown unknowns. An answer from a mentor isn’t just turning a known unknown into something known, it’s potentially batting away dozens of unproductive avenues of exploration. For a small example here, I had run into a lot of cases where string concatenation in the Unison codebase was done with <code>&lt;&gt;</code> instead of <code>++</code>. I was starting to worry - does this represent a significant gap in my understanding? What’s the difference in behaviors? Ian provided the experienced answer that there is no difference in behaviors, Haskell folks just tend to use the more general form of some function if they can to maintain extensibility. A small touch of mentorship batted away what could have been many confused Stack Overflow searches.</p>
<p>I’ve talked a lot about wanting to minimize the cost impact on the maintainers here, which was probably the foremost thought in my mind as I went through this. That’s a worry that can be largely offset by structural motivation for mentorship like you might have starting out at a new job or within academia. The experienced people (professors, TAs, or senior developers) have organizational incentives or mandates to mentor the less experienced people (students or junior developers). Those two places are certainly where my most productive periods of mentorship happened, and I’m constantly reflecting on how much I’ve benefited from the mentorship I’ve received.</p>
<p>There might be less of this structural motivation in OSS, but there is indeed a chance you can find mentorship in an open source community. If you can, then yes, absolutely, get involved and try to contribute. Mentorship can come in many forms, big and small, including PR feedback or responses on Slack. The key is to benefit from other people’s experience as directly as possible. If the maintainers of the project or other active people in the community take time to teach you something, you’ve found something great.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/learning-by-playing</guid>
      <title>Learning by playing</title>
      <description>Recently I experienced a cool first: a project of mine got a shoutout at a programming talk. The project was a small game, and it’s gotten me to reflect on how important making games has been to my development as a developer.</description>
      <link>https://samgqroberts.com/posts/learning-by-playing</link>
      <pubDate>Wed, 24 Feb 2021 15:57:27 GMT</pubDate>
      <content:encoded><![CDATA[<p>Recently I experienced a cool first: a project of mine got a shoutout at a programming talk. The project was a small game, and it’s gotten me to reflect on how important making games has been to my development as a developer.</p>
<h2>
<a name="educational-gamedev-1" class="anchor" href="#educational-gamedev-1"></a>Educational gamedev</h2>
<p>I didn’t set out for this to be the pattern, but it’s somewhat remarkable - over my career as a software developer, almost every foray into a new programming language has been done through trying to build a game. I suppose it’s simply my go-to move when exploring new ways to build software. Making these games not only familiarized me with the nuts and bolts of each of the new programming languages, it also helped introduce me to and familiarize me with generally-applicable software development concepts, the most important of which: I like making stuff that people use.</p>
<h3>
<a name="learning-unison-in-2020-2048httpsgithubcomsamgqroberts2048-unison-2" class="anchor" href="#learning-unison-in-2020-2048httpsgithubcomsamgqroberts2048-unison-2"></a>Learning Unison in 2020: <a href="https://github.com/samgqroberts/2048-unison">2048</a>
</h3>
          <div class="onebox video-onebox">
            <video width="100%" height="100%" controls="">
              <source src="https://i.imgur.com/6YCtyjw.mp4">
              <a href="https://i.imgur.com/6YCtyjw.mp4" rel="noopener">https://i.imgur.com/6YCtyjw.mp4</a>
            </source></video>
          </div>

<p>When I left Tamr in late 2020, I wanted to spend my time studying languages and technologies I hadn’t yet been exposed to. Around that time a couple friends of mine (shoutouts to <a href="https://github.com/rjdellecese">RJ</a> and <a href="https://github.com/seagreen">Ian</a>) were getting involved in a new early-stage functional language called <a href="https://www.unisonweb.org/">Unison</a>. I figured it was a great opportunity to take a deeper dive into functional programming, as well as see how a language gets off the ground.</p>
<p>Initially, I wanted help the community by making a simple game, then building a set of docs around that game to help people learn the language. I chose 2048 because it was sufficiently simple, well-known to people (so they could focus on learning the language), single-player, and didn’t require complex UI interactions (since the only UI feasibly available to me in Unison was the terminal).</p>
<p>I haven’t gotten to the point of building the docs, but the game itself works! Unfortunately I don’t know of a way to host it online, so in order to play it you’ll have to download Unison… and learn a fair bit about how to use its codebase manager. If that’s no problem, <a href="https://github.com/samgqroberts/2048-unison">here’s the repo</a>!</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/fafcf2105174633d00fb658d12cb007ec9495984.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/fafcf2105174633d00fb658d12cb007ec9495984" title="Rúnar&amp;#39;s shoutout"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fafcf2105174633d00fb658d12cb007ec9495984_2_690x326.jpeg" alt="Rúnar's shoutout" data-base62-sha1="zOliEXzW1QrBAeVw0uAdJFYQx12" width="690" height="326" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fafcf2105174633d00fb658d12cb007ec9495984_2_690x326.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/fafcf2105174633d00fb658d12cb007ec9495984_2_1035x489.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/fafcf2105174633d00fb658d12cb007ec9495984_2_1380x652.jpeg 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/fafcf2105174633d00fb658d12cb007ec9495984_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Rúnar&amp;#39;s shoutout</span><span class="informations">1939×917 171 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>Above is a screenshot of the important first I mentioned in the introduction: one of Unison’s cofounders <a href="https://github.com/runarorama">Rúnar Bjarnason</a> gave a <a href="https://www.youtube.com/watch?v=TZZlt6-bKW8&amp;t=95m28s">shout out to this project</a> at a talk he gave recently. It’s a small moment in the scope of a new language like Unison, but I was swelling with pride when I saw it. I need to mention though, in that video he demoes the <code>trunk</code> version that I hadn’t merged an animations update to — it looks better <a href="https://twitter.com/samgqroberts/status/1329876632073547776">with animations</a>.</p>
<h4>
<a name="what-this-introduced-me-to-3" class="anchor" href="#what-this-introduced-me-to-3"></a>What this introduced me to</h4>
<ul>
<li>Everything Unison has to offer, including…
<ul>
<li>Algebraic effects, though do I really understand these? Does anyone? Probably the Unison guys.</li>
<li>Content-addressed immutable term identifiers. The big thing in Unison. The set of ways you can break your program is getting smaller and smaller because really smart people are doing things like this.</li>
<li>Working with a codebase manager, as opposed to a big ol’ bag of text files like we’re used to.</li>
</ul>
</li>
<li>A community built around an alpha programming language. I had never seen what goes into making the sausage before I got involved with Unison, and it’s been very rewarding.</li>
<li>
<a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape sequences</a>, for producing the visual interface in the terminal.</li>
<li>Language design based on Haskell. Trying Unison has coincided with me spending a lot of time learning Haskell, and cross-referencing the two has introduced me to just how influential Haskell is in the FP space.</li>
</ul>
<h3>
<a name="learning-elm-in-2018-rummikubhttpsgithubcomsamgqrobertsrummikub-4" class="anchor" href="#learning-elm-in-2018-rummikubhttpsgithubcomsamgqrobertsrummikub-4"></a>Learning Elm in 2018: <a href="https://github.com/samgqroberts/rummikub">Rummikub</a>
</h3>
<p>Sometime in 2018, my partner and I had her family over for a board game night. We decided to play Rummikub, which sounded fun and all. I had never played it, and I came to find out they had been playing Rummikub as a family for years. Needless to say they absolutely destroyed me…</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/03d449abffe928f461cc6a1c0e7a346758bbc673.jpeg" data-download-href="https://discourse.samgqroberts.com/uploads/default/03d449abffe928f461cc6a1c0e7a346758bbc673" title="...and I took that personally"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/03d449abffe928f461cc6a1c0e7a346758bbc673_2_320x200.jpeg" alt="...and I took that personally" data-base62-sha1="xSfVsE93SKvOtqKAqr1XAtBvPR" width="320" height="200" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/03d449abffe928f461cc6a1c0e7a346758bbc673_2_320x200.jpeg, https://discourse.samgqroberts.com/uploads/default/optimized/1X/03d449abffe928f461cc6a1c0e7a346758bbc673_2_480x300.jpeg 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/03d449abffe928f461cc6a1c0e7a346758bbc673_2_640x400.jpeg 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/03d449abffe928f461cc6a1c0e7a346758bbc673_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">...and I took that personally</span><span class="informations">1200×750 114 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>My hurt pride demanded justice, so I wanted to do something to get more familiar with the rules and mechanics to be better prepared for the next encounter, and I figured building a Rummikub engine would be a nice project. I also wanted to build a solver for it, so that, as I joked to my partner, I could secretly pull it out next time we played Rummikub all together and wow everybody with my unbeatable skills (this never happened).</p>
<p>My friends (shoutouts to <a href="https://github.com/shamshirz">Aaron</a> and <a href="https://github.com/rjdellecese">RJ</a>) were at the time diving into Elm, a new language to me in a yet-unexplored paradigm: “Functional Programming” (does that mean programming that works?). Riding my friends’ energies, I got to work on a Rummikub game engine written in Elm.</p>
<p>I lost steam on the project after only having made the engine, so it unfortunately doesn’t have a solver, a UI or anything screenshot-worthy. But it definitely let me sink my teeth into FP for the first time.</p>
<h4>
<a name="what-this-introduced-me-to-5" class="anchor" href="#what-this-introduced-me-to-5"></a>What this introduced me to</h4>
<ul>
<li>Functional programming! After a heavily OO-weighted education and career to this point, this was my very first foray into this different paradigm. (Spoiler alert, I very much favor it these days). Some concepts under this…
<ul>
<li>Algebraic data types. What would I do without them these days?</li>
<li>Pattern matching.</li>
<li>Automatic currying / the idea that all functions are unary.</li>
</ul>
</li>
<li>Property / fuzz testing. Unfortunately, though, I didn’t really “get” it at the time. I don’t think I was yet primed enough on the importance of laws, properties and guarantees that FP focuses on.</li>
</ul>
<h3>
<a name="learning-c-in-2016-pyxlhttpsgithubcomsamgqrobertspyxl-6" class="anchor" href="#learning-c-in-2016-pyxlhttpsgithubcomsamgqrobertspyxl-6"></a>Learning C++ in 2016: <a href="https://github.com/samgqroberts/pyxl">pyxl</a>
</h3>
          <div class="onebox video-onebox">
            <video width="100%" height="100%" controls="">
              <source src="https://i.imgur.com/WYGmUoR.mp4">
              <a href="https://i.imgur.com/WYGmUoR.mp4" rel="noopener">https://i.imgur.com/WYGmUoR.mp4</a>
            </source></video>
          </div>

<p>This isn’t exactly a game, but it’s not exactly useful either, so I think it fits the category. I had touched C++ a few times in school, but who knows what sticks in those first few confused years of learning how to code. Having worked for the previous few years in Java and JavaScript, I was starting to hear from more experienced colleagues about the relative inefficiencies of Java vs. C++, about how C++ being “lower-level” meant it could be more performant. I figured I should learn about it, and feel what “lower-level” really meant.</p>
<p>I’m not sure exactly where I got the idea for a text file animation engine, but that’s what I built. Honestly, this wasn’t the last time I’d thought about game-like experiences that interact with real-world or not-meant-for-games data or contexts, like in this case arbitrary text files. Perhaps more to come on that…</p>
<p>Anyway, this was the first time I had touched C++ since college in 2014, and I haven’t touched it in the 5 years since. This project is certainly what I recall when thinking about C++ though. If you’d like to try it out you’ll have to pull down the source, build it, then run the binary in your terminal. Try mashing all of the commands at once, the animation engine can handle it :D.</p>
<h4>
<a name="what-this-introduced-me-to-7" class="anchor" href="#what-this-introduced-me-to-7"></a>What this introduced me to</h4>
<p>This project didn’t <em>introduce</em> me to all that much, rather it helped solidify a lot of understanding through gaining a new perspective on fundamental, language-agnostic ideas.</p>
<ul>
<li>Public interface vs. implementation, specifically in the header file vs. cpp file form that concept takes in C++.</li>
<li>Build configuration, specifically in the <code>makefile</code> form it takes in C++.</li>
<li>Complex terminal interactions. This project was my first exposure to <a href="https://en.wikipedia.org/wiki/Ncurses">ncurses</a>, and getting experience here has helped me later, say, use <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape codes</a> to build the 2048 UI.</li>
</ul>
<h3>
<a name="learning-javascript-in-2013-supervirushttpsgithubcomsamgqrobertssupervirus-8" class="anchor" href="#learning-javascript-in-2013-supervirushttpsgithubcomsamgqrobertssupervirus-8"></a>Learning JavaScript in 2013: <a href="https://github.com/samgqroberts/supervirus">Supervirus</a>
</h3>
          <div class="onebox video-onebox">
            <video width="100%" height="100%" controls="">
              <source src="https://i.imgur.com/RK5d2Rv.mp4">
              <a href="https://i.imgur.com/RK5d2Rv.mp4" rel="noopener">https://i.imgur.com/RK5d2Rv.mp4</a>
            </source></video>
          </div>

<p>It was the summer of 2013, and it was a slow week at my internship at <a href="https://koalab.com/">Koa Labs</a>. I was there to develop my skills as a software engineer, and, having almost no experience with it, I decided to try my hand at a small JavaScript project. Since I’m me, that project was a game.</p>
<p>Does anybody remember the old XGen Studios game Fishy?</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/bdfd8dbe7fa54a80b93417b3c8987ef87476911f.png" alt="Fishy Classic" data-base62-sha1="r6JtE98nf0QCiO6JS2oRW5edFrp" width="274" height="200"></p>
<p>I loved that game, so it’s no wonder what I ended up making was essentially a re-skin of it. Nevertheless, I was  and still am very proud of what I had made in those couple weeks. I remember my mentor Kelsey would bet me a dollar each day for the rest of that summer that he could get a higher score than me in a single run. That type of encouragement was awesome, and really helped me understand just how fulfilling it is to have people use and enjoy the stuff I build.</p>
<p>If anybody would like to try Supervirus out, <a href="https://samgqroberts.com/supervirus">here it is</a>! Fair warnings: there’s a (misspelled) expletive if you lose, and you “win” the game when you get so big your browser crashes trying to look for an off-screen place to spawn a new enemy.</p>
<h4>
<a name="what-this-introduced-me-to-9" class="anchor" href="#what-this-introduced-me-to-9"></a>What this introduced me to</h4>
<ul>
<li>Collision detection and resolution, which is a constant consideration in game development.</li>
<li>A return to trigonometry, for the math involved in getting the player character to sliding along the edge of the boundary.</li>
<li>The <a href="https://en.wikipedia.org/wiki/Entity_component_system">entity-component-system</a> gamedev concept, through a JS game engine called <a href="https://craftyjs.com/">craftyjs</a>.</li>
<li>A truth about myself: I want to build things that people find delight in. There’s no feeling quite like that.</li>
</ul>
<p>This also familiarized me with, you know, JavaScript, which proved to be one of the most useful hands-on skills I brought into my job the next year at Tamr. That foundation, and building on it over the course of the next few years, was essential to me becoming a technical leader in the UI there. It’s peculiar to think about how that somewhat frivolous use of two weeks has really fundamentally shaped my career.</p>
<h3>
<a name="learning-java-in-2011-labyrinthadventurehttpsgithubcomsamgqrobertslabyrinthadventure-10" class="anchor" href="#learning-java-in-2011-labyrinthadventurehttpsgithubcomsamgqrobertslabyrinthadventure-10"></a>Learning Java in 2011: <a href="https://github.com/samgqroberts/LabyrinthAdventure">LabyrinthAdventure</a>
</h3>
          <div class="onebox video-onebox">
            <video width="100%" height="100%" controls="">
              <source src="https://i.imgur.com/55LnZ2h.mp4">
              <a href="https://i.imgur.com/55LnZ2h.mp4" rel="noopener">https://i.imgur.com/55LnZ2h.mp4</a>
            </source></video>
          </div>

<p>In the Spring of 2011 I had yet to touch the wonderful world of Computer Science. My friend <a href="https://github.com/shamshirz">Aaron</a> had just taken CS 101 and was showing me the stuff he was doing. It looked like magic. I decided to take CS 101 as a summer course online at Southern Connecticut State University after my freshman year ended.</p>
<p>I distinctly remember the first time I saw “Hello, World!” printed to <a href="https://bluej.org/">BlueJ</a>’s console. Big feeling of magic. It sounds cheesy, but I think from that moment on it was pretty clear to me what I wanted to do with my life. I greatly appreciate that that feeling of magic hasn’t really gone away.</p>
<p>About a month in I made this little gem: <a href="https://github.com/samgqroberts/LabyrinthAdventure">LabyrinthAdventure</a>. I can’t believe I found the source code actually, luckily I had emailed it to a friend asking for advice back in July 2011. This was the very first thing I made that wasn’t a class project. Opening the source code back up again was a powerful blast of nostalgia.</p>
<p>All you do in the game is click on the different directional buttons and see the different messages I put in each location until you get to the end and win. Super modest, but it was a big mountain to climb back then. I remember being so confused about how to work with <a href="https://en.wikipedia.org/wiki/Swing_(Java)">Swing</a>, and just wading blindly into the ocean of copy-pasting answers from the internet. But that’s an important part of learning programming, and maybe any skill - you can’t always gracefully derive actions from well-understood principles, sometimes (maybe, a lot of the time) you have to bang your head against the wall for hours (maybe, years). I developed a sense of accomplishment, and through that motivation, from simply making a thing work. I got to a finished product understanding little but being exposed to everything that went into it, which I think is an important, albeit frustrating, developmental stage.</p>
<h4>
<a name="what-this-introduced-me-to-11" class="anchor" href="#what-this-introduced-me-to-11"></a>What this introduced me to</h4>
<ul>
<li>Building stuff on my own. I can do it!</li>
<li>Java, and programming, in general. There’s so much to soak up one month into your programming education.</li>
<li>The concept of an IDE. After getting advice from a more experienced friend he recommended I use <a href="https://netbeans.org/">NetBeans</a>. Was I ready for it? I’m not really sure, but it launched me into a big new experience for coding.</li>
</ul>
<h2>
<a name="increasingly-complex-legos-12" class="anchor" href="#increasingly-complex-legos-12"></a>Increasingly complex Legos</h2>
<p>Noticing this pattern has made me reflect on what building games offers as a learning mechanism. I think the biggest thing is how important it is to have the spirit of play in learning, especially for me. The fact that the project isn’t for some direct, useful, or external purpose leaves room for not feeling bad about not knowing what you’re doing. The frivolity of it all let’s you explore and try things in a low-stakes environment.</p>
<p>I think in general, not just in regards to learning, I always do my best work in an appropriately playful environment. I think part of the mission of delighting the user means that delight is involved for all those making the experience as well.</p>
<p>Another aspect is that the visual and interactive nature of games lets you create by reacting. I was first exposed to this idea by Bret Victor’s essential essay <a href="http://worrydream.com/LearnableProgramming/">Learnable Programming</a>, which I highly recommend. You change some code, you see your experience change, and you react to that change by tweaking more code. This loop of action and reaction leads to a lot of growth and understanding of what you’re doing.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/responsively-converting-a-sidebar-to-a-navbar-with-react-and-css</guid>
      <title>Responsively converting a sidebar to a navbar with React and CSS</title>
      <description>This is a guided tour of a recent change I made to my own website: converting a sidebar to a top navbar when the user’s screen is small enough. I hope what I’ve learned can help others take their first steps into responsive web design!</description>
      <link>https://samgqroberts.com/posts/responsively-converting-a-sidebar-to-a-navbar-with-react-and-css</link>
      <pubDate>Mon, 04 Jan 2021 21:28:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>This is a guided tour of a recent change I made to my own website: converting a sidebar to a top navbar when the user’s screen is small enough. I hope what I’ve learned can help others take their first steps into responsive web design!</p>
<h2>
<a name="the-goal-1" class="anchor" href="#the-goal-1"></a>The goal</h2>
<p>Given a website with the following general layout:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/a524067b582f12b28dbaef34e17d2285d446c700.png" alt="image (1)" data-base62-sha1="nyTXVhFqzRQa9R8lYX5mmOq6sN2" width="444" height="461"></p>
<p>We will implement a <strong>device breakpoint</strong> that changes the layout for screens with fewer than 700px in width (like mobile phones) to this:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/5dc2fba3c3cf9af3a0a855660ad65aaf2f90bd0f.png" alt="image (2) (1)" data-base62-sha1="dns9xkLh0dErDgdeiIMpSWzQjD1" width="196" height="326"></p>
<p>Furthermore, to conserve space in the smaller navbar, we will hide navigation links behind an expandable menu in the mobile experience, so that this sidebar:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/2d477ec200f8ea97965728ed3c6f5e6e920068a5.png" alt="image (3) (1)" data-base62-sha1="6syF9InttxhxBH6PB8FcKTlDgMd" width="117" height="298"></p>
<p>Turns into a navbar with a menu icon:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/472a1028cbed38192deaeef535659dc5ecf98f7b.png" alt="image (6)" data-base62-sha1="a9y1fKtuDS3yURNWS0nRhw8kj6r" width="187" height="45"></p>
<p>And when the user clicks the menu icon, a menu of navigation links expands down, producing this:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/e228589dfa47e8f421e4df8bd618762f6fefe4dc.png" alt="image (7)" data-base62-sha1="wgGlFKKkcqdzwkdFHGzxZC62E3O" width="191" height="134"></p>
<h3>
<a name="in-action-2" class="anchor" href="#in-action-2"></a>In action</h3>
<p>Since this is what I just did for my live personal site, here’s a demonstration of the device breakpoint, the sidebar converting itself to a top navbar, and the nav menu expansion:</p>
<p><img src="https://discourse.samgqroberts.com/uploads/default/original/1X/c81ce3fc68a34145a4c9f1858f5da81eab7c4794.gif" alt="gifcapture (3)" data-base62-sha1="syhoih40S1nM8aUJKefyLWykyRS" width="690" height="450" class="animated"></p>
<p>Note that here the “Nav Links” in the wireframes are links to my Twitter, Github, and LinkedIn profiles, and the “LOGO” is just a picture of my gorgeous mug.</p>
<h2>
<a name="making-it-happen-3" class="anchor" href="#making-it-happen-3"></a>Making it happen</h2>
<p>I’ll show you how to make this happen in two steps: showing you the full picture of how I actually did it, and then explaining the important technical components of that implementation.</p>
<p>I’ve added annotations and documentation to the <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4">Pull Request where I actually implemented this for my site</a>, so at this point go check that out and see the solution in-situ. Read the description, then look through the code in each commit, keeping in mind the explanation of what that commit is trying to accomplish.</p>
<p>Once you’ve seen it fully come together in the PR, it’ll help for me to decompose the full solution into the important technical components. What follows is a piece-by-piece explanation of the interesting things going on.</p>
<h4>
<a name="using-a-css-media-query-to-implement-a-device-breakpoint-4" class="anchor" href="#using-a-css-media-query-to-implement-a-device-breakpoint-4"></a>Using a CSS media query to implement a device breakpoint</h4>
<p><a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-22fe4f026ca8303947f315a2cfb18bd25d2816ce621abec1aa22cbc0f4e5bf6cR71">The media query</a> in the CSS is the fundamental way that the site behaves differently when the screen’s width is either above or below 700 pixels. Everything outside of that block is always applied, and everything inside that block is only applied if the query returns true, which in this case will only happen if the device is a screen (as opposed to other types of devices like a braille tactile feedback device) and has a minimum width of 700px.</p>
<p>The styles within the media query block will either introduce new styles or override the mobile-first styles above them in order to produce the proper experience for larger screens (eg. moving the top navbar to be a left sidebar).</p>
<h4>
<a name="animating-the-nav-menus-expansion-and-collapse-5" class="anchor" href="#animating-the-nav-menus-expansion-and-collapse-5"></a>Animating the nav menu’s expansion and collapse</h4>
<p>The key is the <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-22fe4f026ca8303947f315a2cfb18bd25d2816ce621abec1aa22cbc0f4e5bf6cR50">transition CSS property</a>. Our property is <code>transition: max-height 0.3s</code>, which means “whenever the <code>max-height</code> property of the selected element (in our case, the <code>.navMenu</code>) changes, animate that change over a period of 0.3 seconds.” We don’t specify a timing function, so the default of “ease” is used.</p>
<p>With that in place, we need to change the <code>max-height</code> property when the menu should be expanded or collapsed, which we do by adding or removing the CSS class <code>.menuOpen</code>. The <code>max-height</code> by default is 0, and with <code>.menuOpen</code> it’s 220px. When <code>.menuOpen</code> is applied, the browser will animate the <code>max-height</code> from 0 to 220px over 0.3 seconds, and the opposite will happen when <code>.menuOpen</code> is unapplied.</p>
<p>Why <code>max-height</code> instead of <code>height</code>? Unfortunately, transitions need exact values in order to operate properly. Ideally we would be able to say <code>height</code> is 0 when the menu is closed, then <code>height</code> is <code>fit-content</code> when the menu is open, but that sadly doesn’t work as far as I can tell. So <code>max-height</code> is a bit of a trick – we specify a value that’s approximately equal to (but a little larger than) the expected height of the expanded  menu. This still lets the layout determine the exact height the nav menu should be. I believe any strategy for setting height directly, using the value that the layout has determined, involves using Javascript to query the height, which I wanted to avoid here in preference of a CSS-only solution.</p>
<h4>
<a name="toggling-a-css-class-of-the-nav-menu-in-react-6" class="anchor" href="#toggling-a-css-class-of-the-nav-menu-in-react-6"></a>Toggling a CSS class of the nav menu in React</h4>
<p>In order to set and unset the <code>.menuOpen</code> class at the appropriate times, we track the <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R16">openness as a React state variable</a> and update it either when the user <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R39">clicks the menu icon</a> or when the user <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R21">clicks outside of the nav menu</a>. We then <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R44">apply the .menuOpen class conditionally</a> based on that state variable, using the ubiquitous <a href="https://www.npmjs.com/package/classnames">classnames</a> NPM package. Note that we’re using CSS modules here, so we have to get the actual CSS class name from the object exported by the CSS file.</p>
<h4>
<a name="detecting-clicks-outside-react-components-7" class="anchor" href="#detecting-clicks-outside-react-components-7"></a>Detecting clicks outside React components</h4>
<p>The PR implements a React hook called <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-cee2b7d0f7418f2b9606ec271bf09b0fdcfa3a4a2925cfc703b4e3d081f010c9R7">useOnClickOutside</a> that detects whether any <code>mousedown</code> or <code>touchstart</code> events have <code>target</code>s that are not contained within the given React refs, then executes the given callback. In our case, we <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R18">give the nav menu and the menu icon as these refs</a>, and our callback <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-6ec596c4f17ad9d1aa9c7a848b0e707f68c0f8704e3f59d5048c3b5687b290e2R21">closes the nav menu</a> (which has no effect if the nav menu is already closed).</p>
<h4>
<a name="rendering-svgs-in-react-8" class="anchor" href="#rendering-svgs-in-react-8"></a>Rendering SVGs in React</h4>
<p>The <a href="https://github.com/samgqroberts/samgqroberts.com/pull/4/files#diff-9ef09bd81042f2c1aa7fbb03c541f2bb577d35dcdcc1048bd036438c6304054eR8">menu icon</a> is implemented as an <code>&lt;svg /&gt;</code> defined directly in React. There are multiple ways to render SVGs in React, the most popular of which that I’ve seen is implementing some type of build tool (eg. webpack) step to understand how to import raw <code>.svg</code> files. I wanted to avoid dealing with build tooling (at all costs, often) here, and opted for this React-only approach.</p>
<p>The definition of the svg itself is from Google’s Material Design icons, specifically <a href="https://material.io/resources/icons/?icon=menu&amp;style=baseline">this one</a>.</p>
<h4>
<a name="a-quick-shout-out-chrome-devtools-9" class="anchor" href="#a-quick-shout-out-chrome-devtools-9"></a>A quick shout-out: Chrome DevTools</h4>
<p>Being comfortable with browser development tooling is an essential part of web development. I use Chrome, so I’ve gotten very familiar with Chrome DevTools (other browsers certainly have their own flavors, I’m just not familiar with them). Using Chrome DevTools is almost as big a part of my web development process as using my text editor (VS Code, if you’re interested).</p>
<p>For what I did here, I made heavy use of its basic features for <a href="https://developers.google.com/web/tools/chrome-devtools/css">inspecting and changing styles</a>, as well as its feature for <a href="https://developers.google.com/web/tools/chrome-devtools/device-mode">testing different screen sizes</a> (the “In action” gif above was created using this Device Mode).</p>
<h2>
<a name="feels-good-to-be-mobile-friendly-10" class="anchor" href="#feels-good-to-be-mobile-friendly-10"></a>Feels good to be mobile-friendly!</h2>
<p>So there you have it. We brought a site from looking almost broken on a smartphone to looking like someone cares about their experience, which is a powerful thing. Personally, I’m just psyched that the 3 whole people that constitute my readership (the friends that I pester to read my stuff) won’t have to read the next one like this:</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/cbda7e523390e4efa8f37ea20d247458bc681071.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/cbda7e523390e4efa8f37ea20d247458bc681071" title="image"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/cbda7e523390e4efa8f37ea20d247458bc681071_2_229x500.png" alt="image" data-base62-sha1="t5n2SU5edoHkYoLhw5oovnX0Ktz" width="229" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/cbda7e523390e4efa8f37ea20d247458bc681071_2_229x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/cbda7e523390e4efa8f37ea20d247458bc681071_2_343x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/cbda7e523390e4efa8f37ea20d247458bc681071_2_458x1000.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/cbda7e523390e4efa8f37ea20d247458bc681071_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">465×1015 87.6 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/the-technology-of-trust</guid>
      <title>The Technology of Trust</title>
      <description>A year or two ago I read Sapiens by Yuval Noah Harari and one passage in particular stuck out to me. Prof. Harari argues that trust, in the form of financial credit, was the key that enabled the explosive growth of the modern economy. I’d like to pose the question: what else does trust enable, and what similarities can we discern among all the things that trust enables?</description>
      <link>https://samgqroberts.com/posts/the-technology-of-trust</link>
      <pubDate>Wed, 30 Dec 2020 23:18:51 GMT</pubDate>
      <content:encoded><![CDATA[<p>A year or two ago I read <a href="https://www.ynharari.com/book/sapiens-2/">Sapiens</a> by Yuval Noah Harari and one passage in particular stuck out to me. Prof. Harari argues that trust, in the form of financial credit, was the key that enabled the explosive growth of the modern economy. I’d like to pose the question: what else does trust enable, and what similarities can we discern among all the things that trust enables?</p>
<h2><a name="p-21-something-from-nothing-1" class="anchor" href="#p-21-something-from-nothing-1"></a>Something from nothing</h2>
<blockquote>
<p>What enables banks - and the entire economy - to survive and flourish is our trust in the future. This trust is the sole backing for most of the money in the world.</p>
<p>– Sapiens, p. 306</p>
</blockquote>
<p>The section in question is at the start of Chapter 16, “The Capitalist Creed,” where Prof. Harari describes a hypothetical interaction between Mr Stone, a builder, Mr Greedy, a banker, and Mrs McDoughnut, a baker.</p>
<p>In this scenario Mr Stone has $1 million in cash on hand and gives it to Mr Greedy to store in the bank. Mrs McDoughnut wants to open a bakery, and receives a $1 million loan from Mr Greedy to build it. Mrs McDoughnut then pays Mr Stone the $1 million to build her the bakery. Mr Stone deposits this new $1 million in his bank account with Mr Greedy.</p>
<p>At this point, Mr Stone has $2 million in his bank account with Mr Greedy. Mr Greedy only has $1 million cash in the account though – the same $1 million that Mr Stone started with, in fact. In a way, Mrs McDoughnut used Mr Stone’s own money to pay him.</p>
<p>It seems paradoxical, almost like one of those puzzles where wooden shapes are rearranged and suddenly there’s a hole in the center. But out of this paradox Mrs McDoughnut has achieved a personal dream, the town has a new building, and the townspeople have access to a new, tasty service.</p>
<p>Prof. Harari points out that the missing piece, the hole in the center of the puzzle, is trust in the future, specifically in the future of the bakery. Mr Greedy trusts that the $1 million loan will return as $2 million as Mrs McDoughnut begins selling baked goods in her new bakery. Mr Stone trusts that the $2 million credit he has in his account with Mr Greedy is equivalent in effect to having $2 million cash in his hand.</p>
<p>If we didn’t have this trust there would be no loans or no credit system, Mr Greedy would not make such investments in ventures like the bakery, and Mrs McDoughnut would only be able to achieve her dream if she personally possessed $1 million, which, if possible in a lifetime, may take years of hard work. Progress and growth would be stuck at a premodern pace.</p>
<h2><a name="p-21-the-trust-in-good-teamwork-2" class="anchor" href="#p-21-the-trust-in-good-teamwork-2"></a>The Trust in Good Teamwork</h2>
<blockquote>
<p>Kathryn pushed on. "The fact is, if we don’t trust one another – and it seems that we don’t – then we cannot be the kind of team that ultimately achieves results. And so that is where we’re going to focus first.</p>
<p>– The Five Dysfunctions of a Team, p. 95</p>
</blockquote>
<p>The trust in Prof. Harari’s example is intangible, effectively a figment of our imagination, and yet it paved the way for something real that tangibly benefits humans. One might call that trust a type of technology, a tool that can be applied to improve a situation.</p>
<p>A little while after reading Sapiens, I read Patrick Lencioni’s <a href="https://www.tablegroup.com/books/dysfunctions/">The Five Dysfunctions of a Team</a>. After reflecting on Mrs McDoughnut’s bakery I was very receptive to the argument Lencioni puts forth that the most fundamental dysfunction of a team of co-workers is “absence of trust.” Without it, the team in the story had no chance of continuing on to master the other qualities of good teamwork.</p>
<p>The idea that trust is the basis of good teamwork <em>and</em> economic growth leads me to wonder just how fundamental and universal trust is. What connections can we draw between the trust that backs a loan of credit and the trust that allows a team to operate effectively? In that common ground could we outline helpful similarities between all trust-based interactions?</p>
<h3><a name="p-21-investing-with-a-teammate-3" class="anchor" href="#p-21-investing-with-a-teammate-3"></a>Investing with a teammate</h3>
<p>To start exploring these potential similarities, let’s consider an interaction between coworkers.</p>
<p>Product Manager at Innovation Inc. has an idea for a new product feature. Product Manager brings this idea to Engineering Lead and asks them to get their team to build it. Engineering Lead’s team spends three months building the first cut of the new feature, based on Product Manager’s ask and vision.</p>
<p>What capital does Engineering Lead have? Engineering Lead has their team’s time and skills, together creating the ability to build Product Manager’s vision into reality. And what capital does Product Manager have? Just the tools to build Engineering Lead’s trust in the vision. This may be a market research slideshow, or a convincing verbal argument, or a shared history of successful ventures.</p>
<p>Some of this equation will certainly be governed by organizational structure – it’s likely that Engineering Lead has some organizational mandate to follow Product Manager’s direction. But insofar as Engineering Lead has the freedom to make decisions, to accelerate or decelerate the process of committing to a vision, this is a trust-based interaction.</p>
<p>Engineering Lead will have to agree to invest their capital in the venture. If it’s a worthy investment it will return more capital in the future, in the form of assets for the business and improved personal reputation, both of which are thereafter available to be invested in future ventures. This positive feedback loop of organizational capital and investment closely mirrors that of financial capital and investment.</p>
<h2><a name="p-21-a-system-of-trust-based-interactions-4" class="anchor" href="#p-21-a-system-of-trust-based-interactions-4"></a>A system of trust-based interactions?</h2>
<p>One similarity we can draw between the financial example and the organizational example is what happens when proof is demanded.</p>
<p>If Mr Stone demands proof that his $2 million is there, Mr Greedy may not be able to do so by literally showing him cash. Mrs McDoughnut may then pull out of the deal, and have to come up with the $1 million herself, which could take a long time and a lot of hard work. Conversely,</p>
<p>If Mr Greedy demands proof that Mrs McDoughnut’s bakery will successfully return the loan money with interest, what can Mrs McDoughnut do? They will have to prove (or be shown the evidence of) her skills as a baker, and that there is a market for her baked goods. This takes time and energy, and perhaps a considerable amount, on both their parts.</p>
<p>In examining the other direction, to aid the analogy, let’s pretend that Mrs McDoughnut has actually solicited a $1 million credit from Mr Greedy’s bank. If Mrs McDoughnut brings this credit to Mr Stone to pay for his services, he has to trust that it’s worth what it says it’s worth. They may both go to Mr Greedy and demand to see proof of this. What can Mr Greedy do? He may not be able to show $1 million cash to back up the credit, but perhaps they can all take the time and energy to analyze the future value of Mr Greedy’s credit to other people in the town, so that Mr Stone can be satisfied that when he uses that credit to buy groceries next week, the grocer will accept it as if it’s cash.</p>
<p>Ultimately, in either demand for proof, there will never be 100% certainty of what the future holds. The bakery venture will always require an amount of trust, and if that minimum amount of trust is missing, the deal will fall through.</p>
<p>Over at Innovation Inc., If Engineering Lead demands proof that Product Manager’s idea is a good one, what can Product Manager do? Similar to the financial example, they can both take the time and energy to prove (or be shown the evidence of) the validity of Product Manager’s vision – taking a look at Product Manager’s market analysis, user research, so on. In the other direction, they can take the time and energy to analyze whether Engineering Lead’s team is up to the task. In either case there’s a chance the proof will not be good enough, the minimum trust in the future isn’t present, and the venture breaks apart.</p>
<p>In both scenarios, and in both directions, we might be able to say that the people involved are claiming that they can back up a capital investment into a venture. This capital investment takes the form of financial credit, effort and baking skills, innovation, or effort and engineering skills. These demands for proof are guards against the risk of believing the claims of each other, specifically the risk that the claims should not be believed. As trust decreases, and proof is demanded, the people involved need to spend more time and energy in the venture. If we consider that time and energy are themselves capital that each person brings to the venture, we may say that as trust decreases, capital efficiency decreases. If trust (and efficiency) hits a critically low threshold, the venture itself may breaks apart.</p>
<h3><a name="p-21-trust-me-ill-keep-thinking-about-this-5" class="anchor" href="#p-21-trust-me-ill-keep-thinking-about-this-5"></a>Trust me, I’ll keep thinking about this</h3>
<p>I’ll end this post here, because my aim for right now is just to introduce the mode of thought and pose the question. So far I feel confident in the claim explored in the previous section, that as (deserved) trust increases, efficiency increases, be that efficiency economic or organizational. But what more can we say about trust-based interactions? Can we describe an overarching system that governs them? Where else do we see examples of trust-based interactions, and what insights can we gain by applying this system? I hope to follow up with another post containing more concrete investigation into these questions.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
    <item>
      <guid isPermaLink="true">https://samgqroberts.com/posts/new-website</guid>
      <title>New website!</title>
      <description>Recently I started a new version of this website from scratch, so here’s the inaugural post kicking off the new era.</description>
      <link>https://samgqroberts.com/posts/new-website</link>
      <pubDate>Sun, 27 Dec 2020 19:11:50 GMT</pubDate>
      <content:encoded><![CDATA[<p>Recently I started a new version of this website from scratch, so here’s the inaugural post kicking off the new era.</p>
<h2>
<a name="the-story-up-until-this-point-1" class="anchor" href="#the-story-up-until-this-point-1"></a>The story, up until this point</h2>
<p>When I graduated college, I joined a small Boston-based startup named <a href="https://www.tamr.com/">Tamr</a> almost immediately and stayed with them for 6 <em>incredible</em> years.</p>
<p>Along the way I tried my hand at “side projects”, in fits and starts. <a href="https://github.com/samgqroberts/headmates">Some</a> exposed me to new technologies, though didn’t go very far as a project. <a href="https://github.com/samgqroberts/pyxl">Some</a> materialized into a what one might call “a thing that does something,” but still didn’t do very much. One of these side projects was a stab at a <a href="https://github.com/samgqroberts/personal-website">personal website</a>.</p>
<p>However the thing about Tamr being my first job out of college, and offering so much in terms of exposure, exploration, expertise, guidance and support to someone who had explored so little at that point, was that I couldn’t help but throw my whole self into it. I got a lot back out, but there was not much of me left for these passion projects, no matter how much I wanted to do this type of self-guided exploration and development.</p>
<p>A couple months ago I left Tamr to see what life looks like when I’m not pouring my whole self into a job. I’m incredibly appreciative to be in a position where I can have a period like this in my life, so I know I need to make the most of it. During this time I want to ratchet up my focus on my health, my personal relationships, and my home life. I also want to pursue the self-guided exploration and production I’d been only tasting before.</p>
<p>This new website is an important component of that last bit. I’d like it to serve as a showcase for my efforts and discovery, and in that way as a source of personal motivation. I’d like it to be a nexus for my “personal brand.”</p>
<h2>
<a name="so-who-is-this-sam-roberts-anyway-2" class="anchor" href="#so-who-is-this-sam-roberts-anyway-2"></a>So who is this “Sam Roberts” anyway?*</h2>
<p><sub>* according to the Google index robots</sub></p>
<p>Looking at building a personal website, one big problem right out the gate is that my name is “Sam Roberts”. Despite the aforementioned side project fits and starts being well-indexable (on sites like Github), if I Google “Sam Roberts” I can’t even page deep enough to find a link referring to me. At page 17 it gives up, still talking about either the <a href="https://en.wikipedia.org/wiki/Sam_Roberts_(radio_personality)">WWE Broadcaster</a> or the <a href="https://en.wikipedia.org/wiki/Sam_Roberts">Canadian musician</a>.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4" title="I don&amp;#39;t remember being a professional wrestler..."><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4_2_690x326.png" alt="image" title="I don't remember being a professional wrestler..." data-base62-sha1="pWOgyXg5Mb7kDigUOoxplGUIHYg" width="690" height="326" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4_2_690x326.png, https://discourse.samgqroberts.com/uploads/default/original/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4.png 1.5x, https://discourse.samgqroberts.com/uploads/default/original/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/b5dc3b681d0009d438ee6f47c32ea32dee9f8ee4_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">I don&amp;#39;t remember being a professional wrestler...</span><span class="informations">979×463 63 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>You know, maybe if I write “Sam Roberts” enough in this post, then when people Google “Sam Roberts” they’ll start to find this “Sam Roberts” instead of the other "Sam Roberts"s. - Sam Roberts</p>
<p>I discovered this uphill battle many years ago, so I chose an online identifier to set me apart from the other imposter Sams: “<strong>samgqroberts</strong>”. Please note that in actuality I do not have even one middle name, and certainly not two. Do you see what my generic name has forced me to do? That all being said, the id has served me pretty well:</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/242dc82089abf442b9016d4604fd435be0021553.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/242dc82089abf442b9016d4604fd435be0021553" title="Now accepting suggestions for what the &amp;#39;gq&amp;#39; stands for"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/242dc82089abf442b9016d4604fd435be0021553_2_407x500.png" alt="image" title="Now accepting suggestions for what the 'gq' stands for" data-base62-sha1="5a3h6sTbBd2moTCYuSNvHdDOQZZ" width="407" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/242dc82089abf442b9016d4604fd435be0021553_2_407x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/242dc82089abf442b9016d4604fd435be0021553_2_610x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/optimized/1X/242dc82089abf442b9016d4604fd435be0021553_2_814x1000.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/242dc82089abf442b9016d4604fd435be0021553_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Now accepting suggestions for what the &amp;#39;gq&amp;#39; stands for</span><span class="informations">1001×1227 162 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>Those are all me, and on the first page of results even. Isn’t that great? And down there at the bottom, that’s my current (as of writing this post) personal website. If I’m going to have even a chance at competing against those other frustratingly-famous Sam Roberts’s that personal website is going to need to do quite the job. It’ll need some shine and novelty on the fronts of content, design, and technology, to say the least.</p>
<h2>
<a name="shine-and-novelty-3" class="anchor" href="#shine-and-novelty-3"></a>Shine and novelty</h2>
<p>So let’s talk about that version of my personal website.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/08b50a3b767124a5b406c592574144c6a2caf127.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/08b50a3b767124a5b406c592574144c6a2caf127" title="&amp;#39;But Aaron&amp;#39;s mom lets him have personal website without the inexplicable query string in the url!&amp;#39;"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/08b50a3b767124a5b406c592574144c6a2caf127_2_595x500.png" alt="image" title="'But Aaron's mom lets him have personal website without the inexplicable query string in the url!'" data-base62-sha1="1f1H9QwtEGlzerabjFOZ9yx3bTN" width="595" height="500" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/08b50a3b767124a5b406c592574144c6a2caf127_2_595x500.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/08b50a3b767124a5b406c592574144c6a2caf127_2_892x750.png 1.5x, https://discourse.samgqroberts.com/uploads/default/original/1X/08b50a3b767124a5b406c592574144c6a2caf127.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/08b50a3b767124a5b406c592574144c6a2caf127_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">&amp;#39;But Aaron&amp;#39;s mom lets him have personal website without the inexplicable query string in the url!&amp;#39;</span><span class="informations">923×775 76.9 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>Ah, just *chef kiss*. The Facebook link, the space in “Linked In”. The Vine link. So stylish in its minimalism.</p>
<p>It has a few things going for it, as in it has links to other manifestations of my personal brand, though I think the Facebook, Spotify and Vine links may not make it into V2. Under the “Projects” tab it displays some of my more showable projects, which is great, and something that I’ll want to bring into V2 somehow.</p>
<p>Overall, though, it’s lacking in content, the design wasn’t particularly well-considered, and the technology behind it needs another pass, which at the onset of another version is where a lot of my focus has been. I first put the old version together in 2015 with React (no Typescript) as a Single Page App. I would build the static assets locally and upload them to a GoDaddy web hosting FTP server. Trying to be nice to my 2015 self I’ll say it’s not a terrible way to go about things, especially for what I knew then, but a fresh look in late 2020 has led me to different choices, both in terms of function and technology.</p>
<h4>
<a name="wwwsamthoughtsgovwwwsamthoughts-check-it-out-4" class="anchor" href="#wwwsamthoughtsgovwwwsamthoughts-check-it-out-4"></a>www.samthoughts.gov.www\samthoughts. check it out.</h4>
<p>The biggest new choice is to feature a blog. After looking around at some options for how to do this, I landed on perhaps a somewhat uncommon approach: I’m using a backing <a href="https://www.discourse.org/">Discourse</a> server to write and store the blog posts as forum topics, then I’m using Discourse’s API to read the content into my website.</p>
<p>This is actually (mostly) what one of Discourse’s founders, <a href="https://en.wikipedia.org/wiki/Jeff_Atwood">Jeff Atwood</a>, does for his excellent blog <a href="https://blog.codinghorror.com/">Coding Horror</a>. You can see at the bottom of his posts he embeds the comments from the blog’s <a href="https://discourse.codinghorror.com/">companion Discourse forum</a>.</p>
<p>I like the idea of supporting discussion with a community of interested people. If I write general “how to use technology X” posts, say, I’d love to be able to have the question “what improvements am I missing?” be asked and answered right there alongside the content. However in this first cut of the website I have left off comment embedding and linking to the Discourse forum - I hope to grow into that.</p>
<h4>
<a name="appeasing-the-index-robots-5" class="anchor" href="#appeasing-the-index-robots-5"></a>Appeasing the index robots</h4>
<p>I’d like my content, especially like those general “how to use technology X” posts, to be easily findable by search engines like Google. Optimizing for this may be easier than legally changing my name (unsure though). Google findability involves building the website with <a href="https://en.wikipedia.org/wiki/Search_engine_optimization">SEO</a> in mind, and unfortunately Single Page Apps with client-side rendering like the old version aren’t great for that. This led me to find a solution that features server-side rendering, but that still uses a technology I’m familiar with. Enter <a href="https://nextjs.org/">Next.js</a>, which has so far proven to be a very easy-to-use, flexible, well documented approach to building React apps with server-side rendering. I was also able to <a href="https://nextjs.org/docs/basic-features/typescript">use Typescript</a> in my Next.js project, which at this point in my technological journey is an absolute must if I’m working in JS-land.</p>
<h4>
<a name="better-deployment-strategy-6" class="anchor" href="#better-deployment-strategy-6"></a>Better deployment strategy</h4>
<p>Manually uploading static assets to an FTP server is so very last decade (or a few decades ago?). These days with the proliferation of cloud-managed solutions there are about a thousand better ways to deploy updates to a website. The way I landed on, especially considering my choice of Next.js, was to just let <a href="https://vercel.com/about">Vercel</a>, the company behind Next.js, handle everything for me (for free!). I gave them permissions to access my Github account and they created a fresh Next.js project for me in a <a href="https://github.com/samgqroberts/samgqroberts.com">newly created repo</a>, complete with webhooks to update both the main site (on push to <code>master</code>) and staging sites (on PR update). Too easy. And did I mention free?</p>
<h4>
<a name="function-and-design-inspirations-7" class="anchor" href="#function-and-design-inspirations-7"></a>Function and design inspirations</h4>
<p>Before I launched into building (like 2015 me did), I first looked around at people I respected to see what they had done for personal websites, and I’d like to point out the three people that have acted as my main sources of inspiration. Of course, there’s Jeff Atwood and Coding Horror, particularly for the how-to-use-Discourse-for-a-blog aspect. There’s also <a href="https://pchiusano.github.io/">Paul Chiusano</a>, who I’ve had the great pleasure of meeting recently while alpha testing his incredible project <a href="https://www.unisonweb.org/">Unison</a>. And there’s <a href="https://jaredforsyth.com/">Jared Forsyth</a>, who I’ve never met, but whose projects have absolutely wowed me, particularly <a href="https://github.com/jaredly/unison.rs">unison.rs</a>.</p>
<h2>
<a name="hello-world-8" class="anchor" href="#hello-world-8"></a>Hello, world</h2>
<p>So, here we have it: the maximally embarrassing very first cut of my new website.</p>
<p><div class="lightbox-wrapper"><a class="lightbox" href="https://discourse.samgqroberts.com/uploads/default/original/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7.png" data-download-href="https://discourse.samgqroberts.com/uploads/default/4d52e9b67e242a560ddec25b5155ebecc130fdb7" title="This blog post features a picture of itself!"><img src="https://discourse.samgqroberts.com/uploads/default/optimized/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7_2_689x445.png" alt="image" title="This blog post features a picture of itself!" data-base62-sha1="b22p8hFD9dwTtZSTbmFr4kt1tLp" width="689" height="445" srcset="https://discourse.samgqroberts.com/uploads/default/optimized/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7_2_689x445.png, https://discourse.samgqroberts.com/uploads/default/optimized/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7_2_1033x667.png 1.5x, https://discourse.samgqroberts.com/uploads/default/original/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7.png 2x" data-small-upload="https://discourse.samgqroberts.com/uploads/default/optimized/1X/4d52e9b67e242a560ddec25b5155ebecc130fdb7_2_10x10.png"><div class="meta">
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">This blog post features a picture of itself!</span><span class="informations">1279×826 123 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg>
</div></a></div></p>
<p>Despite being even sparser than the old version, it’s a start, built on technology I feel confident in and comfortable with. And now that it’s out there, my embarrassment will mount unless I continuously improve its content and design - a classic motivation trap. I can already feel future Sam cursing me. It feels good.</p>
<p><strong>Thank you for reading!</strong></p>]]></content:encoded>
    </item>
  
      </channel>
    </rss>
  