Check whether your feed is valid. For more information about JSON Feed, see the specification.
{ "version": "https://jsonfeed.org/version/1.1", "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — https://www.bryanbraun.com/feed.json — and add it your reader.", "home_page_url": "https://www.bryanbraun.com", "title": "Bryan Braun - Blog", "description": "Making things and sharing what I learn.", "feed_url": "https://www.bryanbraun.com/feed.json", "favicon": "https://www.bryanbraun.com/assets/images/feed-favicon.png", "icon": "https://www.bryanbraun.com/assets/images/feed-icon.png", "language": "en-US", "authors": [ { "name": "Bryan Braun" } ], "items": [ { "id": "/2025/07/03/a-new-job-a-new-life", "url": "https://www.bryanbraun.com/2025/07/03/a-new-job-a-new-life/", "date_published": "2025-07-03T00:00:00-04:00", "date_modified": "2025-07-03T00:00:00-04:00", "title": "A new job, a new life", "summary": "Last month, I started a new job working as a fullstack software engineer at ClassDojo.", "content_html": "<p>Last month, I started a new job working as a fullstack software engineer at ClassDojo.</p><p><img src=\"https://www.bryanbraun.com/assets/images/classdojo-logo.png\" alt=\"ClassDojo Logo\" /></p><p>This was an interesting job transition for me. It was the first time I got caught up in layoffs and the first time that I ended up with multiple job offers to choose from.</p><p>As I weighed the decisions in front of me, I was reminded of <a href=\"https://x.com/waitbutwhy/status/1367871165319049221?lang=en\">an illustration from Tim Urban</a> (of Wait but Why), that captured my feelings perfectly:</p><p><a href=\"https://x.com/waitbutwhy/status/1367871165319049221?lang=en\"><img src=\"https://www.bryanbraun.com/assets/images/life-paths.jpg\" alt=\"\" /></a></p><p>My job offers revealed the beginnings of several wildly diverging paths, which reminded me that a future of diverse opportunities remained stretched out in front of me. What an exciting thing to contemplate.</p><p>When I was in college, my roommates and I had a running joke. Whenever some tiny setback would occur, one of us would smile and say “the good times are over.” The absurdity of it got us laughing, because we had our whole lives ahead of us. Clearly, things were going to be ok.</p><p>These days, “the good times are over” is a common narrative in the world—treated seriously, not as a joke. It can be tempting to fall into that way of thinking.</p><p>But if we could see more clearly, we’d realize that a world of branching opportunities still stretches out before us, both individually and collectively. One of the key reasons I chose ClassDojo was because I saw a team that was full of energy and optimism for the future ahead. That’s <a href=\"https://www.bryanbraun.com/2021/05/21/the-rivers-we-swim-in/\">a river I want to swim in</a>.</p><p>I’m excited to see where this path leads.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/07/03/a-new-job-a-new-life/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/06/28/lessons-learned-running-a-family-minecraft-server", "url": "https://www.bryanbraun.com/2025/06/28/lessons-learned-running-a-family-minecraft-server/", "date_published": "2025-06-28T00:00:00-04:00", "date_modified": "2025-06-28T00:00:00-04:00", "title": "Lessons learned from five years of running a family Minecraft server", "summary": "For the past five years, I’ve been running a family Minecraft server. The server began as a one-year experiment during the pandemic but quickly became an indispensable “third place” for our kids to spend time with friends and extended family who lived far away. I’m writing this post to share our research and lessons learned, in case it’s useful to anyone else.", "content_html": "<p>For the past five years, I’ve been running a family Minecraft server. The server began as a one-year experiment during the pandemic but quickly became an indispensable “<a href=\"https://en.wikipedia.org/wiki/Third_place\">third place</a>” for our kids to spend time with friends and extended family who lived far away. I’m writing this post to share our research and lessons learned, in case it’s useful to anyone else.</p><h2 id=\"overview\">Overview</h2><h3 id=\"project-goals\">Project goals</h3><p>The goal behind this server was to create a fun and safe virtual space for our kids to spend time with distant family members and friends. There are lots of public Minecraft servers out there, many which are very active and have heavily customized Minecraft to create RPGs, mini-games, team sports, and more. Instead of trying to recreate something like that, we focused on building a private space to play Minecraft in all of it’s many forms (various gamemodes, rulesets, enhancements, etc).</p><h3 id=\"project-requirements\">Project requirements</h3><ul> <li>The server should use Minecraft Java edition. We prefer Java edition for it’s superior creative ecosystem, including mods, plugins, resource packs, and more (for details, see <a href=\"https://www.bryanbraun.com2024/03/08/why-we-prefer-computers-over-consoles-when-introducing-kids-to-gaming/\">Why we prefer computers over consoles when introducing kids to gaming</a>).</li> <li>The server should be private.</li> <li>The server should give us admins full creative control to design the ideal playing experience for our family. We should be able to play in whatever worlds we want to, in any gamemode, with any ruleset we want.</li> <li>The server should be relatively low-maintenance.</li> <li>The server should be relatively inexpensive.</li></ul><h2 id=\"early-decisions\">Early decisions</h2><h3 id=\"hosting\">Hosting</h3><p>When it comes to hosting, the level of maintenance vs control is a trade-off and we were willing to adopt some maintenance overhead if it gave us a lot of control over our gaming environment. With that it mind, here are the hosting options we looked at:</p><ul> <li>1st-party hosted world (Realms, etc) - Low maintenance, low control</li> <li>3rd-party Minecraft-specific server rental (Shockbyte, etc) - Medium maintenance, high control</li> <li>3rd-party generic server rental (Digital Ocean, etc)- High maintenance, high control</li> <li>Full self-hosted (Raspberry Pi, etc) - Extremely high maintenance, highest control</li></ul><p>A service like Realms offers a polished experience, but it seemed too limiting for us (it’s a single vanilla Minecrat world, with low customization). That said, managing all the code and dependencies on a Linux machine in my basement seemed unsustainable. We decided on a $5/month server from Shockbyte. Serious server maintainers like to hate on Shockbyte because it’s shared hosting. I get it—us web-devs have similar opinions about services like BlueHost and HostGator. But for a small, private, family server, Shockbyte was perfect. Here’s a few things I liked about it:</p><ul> <li>A convenient dashboard that allows server maintenance from any web browser.</li> <li>A dedicated UI for Minecraft-specific server maintenance needs (Minecraft version management, Plugin browser, User management, etc)</li> <li>In-browser server console</li> <li>FTP access for convenient file management and backups</li></ul><p>In short, it gave us all the control we needed while reducing a bunch of the overhead.</p><h3 id=\"minecraft-server-type\">Minecraft server-type</h3><p>In Java edition, there are <a href=\"https://www.spigotmc.org/wiki/what-is-spigot-craftbukkit-bukkit-vanilla-forg/\">multiple flavors of Minecraft that you can run on a server</a>. These extend the original Minecraft server code, preserving the original game while adding new APIs to make Minecraft more extendable for developers. Here are a few of the server types we looked at:</p><ul> <li><strong>Vanilla</strong>: The original Minecraft game, without much customization.</li> <li><strong>Forge</strong>: Forge means mods. Mods are powerful and can dramatically change the game, but in order for a user to join a Forge server, they are usually required to run Forge locally, along with the mods that the server is running.</li> <li><strong>Spigot/Bukkit</strong>: Supports “Plugins” which offers medium customization and can be run on the server, without requiring users to run them locally. Spigot and Bukkit have differences but are largely cross-compatible.</li> <li><strong>Paper</strong>: A high-performance fork of Spigot, with various fixes and additional configuration options.</li> <li>…<a href=\"https://www.spigotmc.org/wiki/what-is-spigot-craftbukkit-bukkit-vanilla-forg/\">many others</a></li></ul><p>In our case, we went with Spigot. The plugins allowed us to customize the environment while still making it easy for users to join (which reduced the amount of tech support we’d need to provide to grandparents, etc).</p><h3 id=\"plugins\">Plugins</h3><p>The Minecraft Spigot/Bukkit community has a large library of plugins that you can enable on your server to customize the game. These are largely free, open-source, and built by hobbyists. Even with Shockbyte’s plugin browser, it took some intermediate technical skill to find and manage plugins for our server. The main challenge is ensuring that the plugins you use are compatible with your version of Minecraft and with each other. Minecraft has been around for 15 years, which means that there are a lot of old plugins out there that no longer run on the latest version of Minecraft. A failing plugin might leave you with an obscure error message in your console, or maybe no error at all. Like all open source, support is unreliable and documentation is mixed, so you’re on the hook to resolve your own issues.</p><p>For us, it was worth it. We tried many plugins and our favorites became indispensable to how we played Minecraft on the server. Here’s a list of the ones we used the most:</p><ul> <li><strong>Multiverse</strong> - Tools for managing multiple Minecraft worlds on a single instance (more on this below)</li> <li><strong>Multiverse Portals</strong> - Portals for traveling between multiverse worlds</li> <li><strong>Multiverse SignPortals</strong> - Allows Minecraft “signs” to act as multiverse portals</li> <li><strong>Multiverse Inventories</strong> - Allows independent inventories in each multiverse world</li> <li><strong>WorldEdit</strong> - Gives you crafting superpowers, like bulk-edit, cut/copy/paste, commands for building shapes, and more</li> <li><strong>CustomImages</strong> Allows you to make any image into a Minecraft “painting”</li> <li><strong>Dynmap</strong> - Think “Google Maps for Minecraft”</li> <li><strong>Grief Prevention</strong> - Configurable player restrictions that you can place in a world to prevent trolling/griefing (with good defaults)</li> <li><strong>LuckPerms</strong> - Highly configurable permissions, allowing you to create roles for different kinds of users</li> <li><strong>ChairsReloaded</strong> - Enable users to build chairs and sit in them… it’s simple, but strangely endearing</li></ul><h2 id=\"designing-a-virtual-play-space-one-server-many-worlds\">Designing a virtual play-space (“one server, many worlds”)</h2><p>I wanted our server to accommodate all of the different ways people might want to play Minecraft. We ended up creating a network of worlds, powered by plugins like <a href=\"https://dev.bukkit.org/projects/multiverse-core\">Multiverse</a> and <a href=\"https://luckperms.net/\">LuckPerms</a>. This setup evolved over time, but mapping it out would look something like this:</p><p><img src=\"https://www.bryanbraun.com/assets/images/minecraft_server_worlds.png\" alt=\"A diagram showing each world on the server and how they are connected\" /></p><p>Describing each world briefly…</p><ul> <li><strong>Land_of_Braun</strong> - A creative paradise, and the central hub for the server</li> <li><strong>adventure_time</strong> - An entry-level survival world for young kids, with strong protections for builds, grief-prevention, and anti-cheating measures</li> <li><strong>hard_times</strong> - A hard-mode survival world, for experienced players</li> <li><strong>2007, nerd_night, peter_and_cameron</strong> - One-off survival worlds for various friend groups</li></ul><p>With these worlds, we could invite friends with a wide variety of ages and skill-levels, and usually find something they would enjoy doing.</p><p>In Land_of_Braun, at the spawn location, we built “Grand Central Station” to serve as a transportation hub. Inside it, we set up a wall of “sign portals” for teleporting to each of the other worlds.</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/grand-central-station.jpg\" /> <figcaption>Grand Central Station, our server's transportation hub.</figcaption></figure><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/sign-portal-worlds.jpg\" /> <figcaption>Sign portals for teleporting to each world. <br />We removed one-off worlds when finished with them to preserve server resources (more on this below).</figcaption></figure><p>As Land_of_Braun filled up with interesting builds, we also created sign portals for teleporting to many of these points of interest.</p><p><img src=\"https://www.bryanbraun.com/assets/images/sign-portals.jpg\" alt=\"A wall of sign portals used for teleportation within the Land_of_Braun world\" /></p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/peachs-castle.jpg\" alt=\"Peach's Castle from Mario 64, built in Minecraft\" /> <figcaption>Peach's Castle</figcaption></figure><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/itza.jpg\" alt=\"Itza, built in Minecraft\" /> <figcaption><a href=\"https://en.wikipedia.org/wiki/Chichen_Itza\" target=\"_blank\">Chichén Itzá</a></figcaption></figure><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/star-valley-temple-minecraft.jpg\" alt=\"Star Valley Temple, built in Minecraft\" /> <figcaption>Star Valley Temple</figcaption></figure><figure class=\"center\"> <video controls=\"\" onended=\"this.currentTime = 0\" style=\"max-width: 100%\"> <source src=\"https://www.bryanbraun.com/assets/video/ice-castle-flyby.webm\" type=\"video/webm\" /> Your browser does not support the video tag. </video> <figcaption>Ice-castle, built into the side of a mountain</figcaption></figure><p>As I’ve hosted many remote play sessions, I’ve discovered some ways to make them more successful:</p><ul> <li>It’s significantly more fun to talk out loud while you play. We found that Discord and Facetime usually worked the best for this (better than Zoom/Google Meet/phones).</li> <li>When playing in creative mode, find a building prompt that everybody can participate in (regardless of age and skill level). Some examples included: <ul> <li>City-building, where each person gets their own city block to build on. This prompt can span multiple play sessions (ie, expanding the city) and supports several variations (Normal city, “Halloween town”, Medieval village, Winter city, etc).</li> <li>“Let’s build a castle together.” Break it into parts that each person can work on (the grounds, the moat, the dungeon, etc). This can work for non-castles too, as long as it can be clearly subdivided.</li> <li>Everyone build your dream house (and then give tours).</li> </ul> </li> <li>Some commands ended up being so useful that most everyone playing on our creative world learned them: <ul> <li><code class=\"language-plaintext highlighter-rouge\">/gspawn</code>: for returning to grand central station</li> <li><code class=\"language-plaintext highlighter-rouge\">/tp</code> or <code class=\"language-plaintext highlighter-rouge\">/mvtp</code>: for teleporting to different worlds, locations, and people (or teleporting people to you)</li> <li><code class=\"language-plaintext highlighter-rouge\">/thru</code>: teleports yourself through an obstacle immediately in front of you.</li> <li>Many WorldEdit commands (<code class=\"language-plaintext highlighter-rouge\">/copy</code>, <code class=\"language-plaintext highlighter-rouge\">/paste</code>, <code class=\"language-plaintext highlighter-rouge\">/set</code>, etc)</li> </ul> </li> <li>As the server admin, it’s good to play on the server with the other players so you can see their pain-points and adjust game/world settings to address them. For example, in typical Minecraft survival, all players must be asleep and in beds in order for the game to skip through the night to the next day. I saw that this often caused conflict in our games (often some people wanted to sleep, but others didn’t want to stop what they were doing). We were able to resolve most conflicts by changing <code class=\"language-plaintext highlighter-rouge\">playersSleepingPercentage</code> to a majority value, like 51% (meaning, if a majority of the players are sleeping, the game would advance to the next day).</li> <li>Refining permissions takes effort but it is worth it, because bad permissions can lead to a lot of issues that takes all the fun out of playing (cheating, griefing, vandalism, etc).</li></ul><h2 id=\"being-a-server-admin\">Being a server admin</h2><p>When using a 3rd-party hosting provider like Shockbyte, you’re the one responsible for maintaining your server and keeping it running. Here’s a few things I learned throughout that process.</p><h3 id=\"dad-the-server-went-down\">“Dad, the server went down”</h3><p>If the server goes down for some reason, a simple restart may fix it. Hosts like Shockbyte make this as easy a clicking a button in the control panel.</p><p>If a restart doesn’t work, check the console for error messages.</p><p>At one time, we kept seeing a message: <code class=\"language-plaintext highlighter-rouge\">Can't keep up! Is the server overloaded? Running 2867ms or 57 ticks behind.</code> Our server was facing memory constraints.</p><p><a href=\"https://shockbyte.com/help/knowledgebase/articles/minecraft-server-can-t-keep-up\">Memory constraints can happen for many reasons</a>, but in this case, it was caused by a recent update to a newer version of Minecraft with a higher memory requirement. We made some adjustments which addressed the issue for a while, but eventually we needed to upgrade to a server with more RAM.</p><p>At another time, we noticed our server struggling, and discovered that we were approaching the file storage limit for our hosting plan. Looking closer, we saw that we had some large files leftover from older Minecraft versions we had run in the past. We also saw that we had some worlds we were no longer using (worlds can be pretty heavy, space-wise). Deleting these old versions and worlds gave us plenty of space to work with.</p><p>Finally, I should mention the time that a curious kid used WorldEdit to build a massive sphere of TNT. As they gleefully detonated it, the rendering of the explosion slowed the server to a crawl (also a memory issue). In this case, we needed to adjust the render settings to simplify the explosion and resolve it one portion at a time. Once the explosion finished, everything went back to normal. 🥵</p><h3 id=\"updating-minecraft\">Updating Minecraft</h3><p>Visitors to your server need to run the same version of Minecraft that <em>you</em> run on the server. Minecraft receives updates often, which means that you’ll need to update your server-Minecraft regularly or be OK with running older versions. We’ve done the update several times at this point, so here’s my advice for anyone doing Minecraft server updates:</p><ul> <li>Always back up your server before running updates! (more on this below)</li> <li>Before attempting an update, check to see if your dependencies (ie. plugins) are compatible with the new version. Updating Minecraft usually means you need to update your plugins. If an important plugin doesn’t support your target Minecraft version, then you should probably wait to update. The more plugins you use, the more likely it is that you’ll need to trail the latest Minecraft version.</li> <li>After you update and restart your server, watch for error messages in your console. It’s the easiest way to tell if something is broken and how you might fix it.</li></ul><h3 id=\"running-backups\">Running backups</h3><p>In the five years we’ve managed our server, the worst incident was when an errant WorldEdit command converted an enormous number of existing blocks in the Land_of_Braun into “Iron Bars”. In the process, the server crashed, making it impossible to reverse the action with a WorldEdit “undo” command. Worst of all, we didn’t have a recent backup. 😱</p><p>Periodic backups are essential if you’re running a server. Fortunately, many Minecraft server hosts have a backup feature feature built-in. Our host (Shockbyte), allows you to schedule backups on a set interval, automatically replacing the oldest back-up when you reach a configurable limit. Super convenient! <img src=\"https://www.bryanbraun.com/assets/images/minecraft-server-backups.png\" alt=\"\" /></p><p>If your host does not offer automatic backups, a simple solution is to connect to your server with an FTP client (like Filezilla), and periodically copy all server files down to your local computer. This was our primary backup method for awhile, and it’s what ended up saving us from the iron-apocalypse (we eventually found a semi-recent backup we could restore to).</p><h2 id=\"conclusion\">Conclusion</h2><p>As far as Minecraft servers go, ours is pretty simple, but it still required a lot of learning. Our server continues to evolve as we discover more and better ways to play. Have you ever managed a family Minecraft server? How did it go? What worked for you?</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/06/28/lessons-learned-running-a-family-minecraft-server/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/06/21/waiting-is-risky", "url": "https://www.bryanbraun.com/2025/06/21/waiting-is-risky/", "date_published": "2025-06-21T00:00:00-04:00", "date_modified": "2025-06-21T00:00:00-04:00", "title": "Waiting is risky", "summary": "I’m a note-taker and a list-maker. I have lists of books I want to read and movies I want to watch, but most of my lists are for collecting ideas. They include ideas for blog posts, websites, businesses, open-source code, video games, books, and other kinds of projects.", "content_html": "<p>I’m a note-taker and a list-maker. I have lists of books I want to read and movies I want to watch, but most of my lists are for collecting ideas. They include ideas for blog posts, websites, businesses, open-source code, video games, books, and other kinds of projects.</p><p>Some of these ideas, the really good ones, have been gestating for years. I find myself returning to them often to add details as I think of them, continually fleshing them out, and refining them. Some have pages of notes by this point. I tell myself that when the time is right for a new post, project, business or book, I can pick up the idea and run with it.</p><p>Sometimes it works pretty well. Both <a href=\"https://www.bryanbraun.com/2020/09/08/gridmaster-closing-thoughts\">Gridmaster</a> and <a href=\"https://musicbox.fun\">Music Box Fun</a> were long-gestating project ideas that I built and launched when the time was right.</p><p>But I’ve also learned that waiting for the right time is risky.</p><p>For one, sometimes <strong>the world changes</strong>, and your idea no longer makes sense.</p><p>I once drafted a blog post about some quirky behaviors in <code class=\"language-plaintext highlighter-rouge\">npm</code>, but by the time I came back to refine it, the behaviors had been fixed. Another time, I had an idea for a podcast, only <a href=\"https://www.bryanbraun.com/2014/02/14/tldr/\">to watch somebody else create it</a> (they did a <em>great</em> job). Most recently, I’ve found that several of my project ideas can be replaced by a simple AI prompt (like, <em>“Generate some lorem ipsum text in the style of Dr. Seuss”</em>).</p><p>But the other reason that waiting is risky, is that <strong>you</strong> change.</p><p>I’ve got several ideas for children’s books, but I can feel my interest in them waning as my kids get older. Several of the business ideas I added grew out of pain points that I no longer have. And when I look at some of the more ambitious projects, I wonder if I even still have the appetite to take a swing at them.</p><p>Maybe it’s good that I didn’t waste my time building some niche thing only to have it replaced by AI. Maybe I dodged a bullet by not committing to a business I would have grown out of.</p><p>But when I look at my freshest, most exciting ideas—it pains me to know that if I don’t build them now, I might never do it, because I’ll never feel as passionate about them as I do today.</p><!-- I couldn't find a good place to add this link to the post, so I'm including it in a comment --><!-- Inspiration expires: https://x.com/BryanEBraun/status/1395948579542470656 --><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/06/21/waiting-is-risky/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/04/15/links-12", "url": "https://www.bryanbraun.com/2025/04/15/links-12/", "date_published": "2025-04-15T00:00:00-04:00", "date_modified": "2025-04-15T00:00:00-04:00", "title": "Links #12", "summary": "Oscar, an open-source contributor agent architecture - Oscar is a project from the Go community that aims to develop AI agents that assist open source maintainers. I think this is a great idea. Open source is a load-bearing pillar in our modern digital infrastructure and maintainers need help. If AI agents were able to reduce the maintenance burden, it could reduce burnout, improve project longevity, and encourage new development. Excited to see where this goes.", "content_html": "<p><strong><a href=\"https://go.googlesource.com/oscar/+/refs/heads/master/README.md\">Oscar, an open-source contributor agent architecture</a></strong> - Oscar is a project from the Go community that aims to develop AI agents that assist open source maintainers. I think this is a great idea. Open source is a load-bearing pillar in our modern digital infrastructure and <a href=\"https://xkcd.com/2347/\">maintainers need help</a>. If AI agents were able to reduce the maintenance burden, it could reduce burnout, improve project longevity, and encourage new development. Excited to see where this goes.</p><p><strong><a href=\"https://antfu.me/posts/move-on-to-esm-only\">Move on to ESM-only</a></strong> - A proper update on JavaScript’s move-to-ESM fiasco. I was pleasantly surprised to learn that tools like Vite are helping push the community migration forward. One clear takeaway is that new packages should publish in ESM-only (no dual-publishing of ESM + CommonJS). Last week, I ended up republishing <a href=\"https://github.com/bryanbraun/checkboxland\">Checkboxland</a> as ESM-only, in large part due to the influence of this post.</p><p><strong><a href=\"https://cowboy.codes/blog/keyboard-journey/\">My keyboard ergonomics journey as an engineer</a></strong> - A nice write-up on keyboard ergonomics from my former co-worker Grant. This post inspired me to experiment with keyboards, and I’ve been using a split keyboard for the past two months now. Thanks Grant!</p><p><strong><a href=\"https://ludic.mataroa.blog/blog/you-must-read-at-least-one-book-to-ride/\">You Must Read at Least One Book To Ride</a></strong> - The basic message here is that there’s an astounding amount of mediocrity in our industry (all industries, really), and all it takes is reading one book in a relevant topic for your work to make you stand out. Compelling, if true!</p><p><strong><a href=\"https://www.notboring.co/p/radiant\">Radiant</a></strong> - A technical deep dive into a startup that is working to replace diesel generators with portable nuclear reactors the size of a shipping container. As I read, I found myself carried away by the narrative and detailed explanations of the problem space and potential solutions. The article comes from tech investor Packy McCormick’s Substack blog, which explains the techno-optimism. Honestly, I’m here for it. He’s writing about people pushing the frontiers in fields that actually matter like energy, transportation, and biomedicine. These are things that will help people live healthier lives with fewer costs and more personal freedoms. Good reading, if you need that shot of optimism from time to time.</p><p><strong><a href=\"https://grantslatton.com/nobody-cares\">Nobody Cares</a></strong> -A proper rant/observation about how so many things in the world could be better if people cared more. It got me thinking about the times when I did my best work and the other times when I didn’t. My working theory is that it’s not that people don’t care… it’s more that they don’t care about the same things. The streetlight installer in his post cares more about driver experience than pedestrian experience. The bureaucrat cares more about getting home by 5 to make dinner for their sick spouse, than working late to push for alternative bike ramp designs. What makes Japan “nice” isn’t that they care more… it’s that they care about the same things more. Seems plausible, right?</p><p><strong><a href=\"https://blog.benjaminreinhardt.com/young-people-technical-training\">Precocious Young People Should Do Deep Technical Training</a></strong> - <em>“Science and technology drive the modern world. If you understand how they work, you can become a much more active participant in the world, instead of being at the mercy of what is effectively magic.”</em> Of all the things I learned in the years I spent studying mechanical engineering, the most important was that <em>I can understand anything… it’s just a matter of desire and time.</em> I don’t know if that realization ever comes unless you’ve battled your way to an understanding of at least a couple highly technical subjects.</p><p><strong><a href=\"https://www.astralcodexten.com/p/the-colors-of-her-coat\">The Colors Of Her Coat</a></strong> - This post left me awestruck. Through a series of stories, Scott Alexander makes a case that the long march of human advancement is methodically removing our opportunities to experience wonder. Modern humans no longer feel ecstasy when they drink a spiced beverage, hear an opera singer, or see an AI-generated portrait, because these wonders are no longer scarce and scarcity is what gives things value. So what happens when we finally arrive at our post-scarcity utopia (whether that be via earthly technology or spiritual afterlife)? Are we in heaven or hell? It’s a fascinating discussion, full of examples, poetry, and religious symbolism.</p><p><strong><a href=\"https://www.lightnote.co/\">LightNote</a></strong> - Interactive music theory lessons in the browser. Try the free sample… it’s pretty fun. I’m kinda jealous that I didn’t build this.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/04/15/links-12/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/03/29/breaking-down-circular-dependencies-javascript", "url": "https://www.bryanbraun.com/2025/03/29/breaking-down-circular-dependencies-javascript/", "date_published": "2025-03-29T00:00:00-04:00", "date_modified": "2025-03-29T00:00:00-04:00", "title": "Breaking down circular dependencies in JavaScript", "summary": "I write a lot of JavaScript but circular dependencies have always been kind of a mystery to me. The error messages always seem random and inscrutable, and sometimes there’s no error message at all! I wanted to understand this topic better, so I ran a series of experiments and wanted to share what I learned. Let’s break down circular dependencies in JavaScript.", "content_html": "<p>I write a lot of JavaScript but circular dependencies have always been kind of a mystery to me. The error messages always seem random and inscrutable, and sometimes there’s no error message at all! I wanted to understand this topic better, so I ran <a href=\"https://github.com/bryanbraun/circular\">a series of experiments</a> and wanted to share what I learned. Let’s break down circular dependencies in JavaScript.</p><h2 id=\"what-are-circular-dependencies\">What are circular dependencies?</h2><p>Circular dependencies happen when your JavaScript <code class=\"language-plaintext highlighter-rouge\">import</code> statements result in a loop:</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/circular-dependencies.svg\" alt=\"Diagram showing a two-way dependency loop and a three-way dependency loop.\" /> <figcaption>The loop can consist of two files, three files, or more.</figcaption></figure><p>Any time your <code class=\"language-plaintext highlighter-rouge\">import</code> statements create a loop like this, there’s a risk that your code won’t work as expected.</p><h2 id=\"how-do-you-know-when-you-have-circular-dependencies\">How do you know when you have circular dependencies?</h2><p>There’s no easy way built into the language!</p><p>In JavaScript, a circular dependency often manifests as a seemingly unrelated error (like a <code class=\"language-plaintext highlighter-rouge\">ReferenceError</code> or <code class=\"language-plaintext highlighter-rouge\">TypeError</code>). This is different from many other programming languages, which often tell you directly that your imports are bad:</p><ul> <li>Python: <code class=\"language-plaintext highlighter-rouge\">ImportError</code></li> <li>Go: <code class=\"language-plaintext highlighter-rouge\">import cycle not allowed</code></li></ul><p>So why can’t JavaScript come out and say ⚠️ <code class=\"language-plaintext highlighter-rouge\">CircularDependencyError</code>?</p><p>It’s because JavaScript modules are designed to be loaded and executed on-the-fly.</p><p>When your browser loads a web page and starts executing its first JavaScript file, it has no idea how many more files are still coming. Those files could still be sitting on servers on the other side of the world.</p><p>This is a very different situation than a Go or Python program, where the import system can analyze the whole dependency tree before executing a single line of code.</p><h2 id=\"stepping-through-a-circular-dependency-in-javascript\">Stepping through a circular dependency in JavaScript</h2><p>The best way to explain the errors that JavaScript gives us is to step through a circular dependency scenario:</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/circular-dependencies.png\" alt=\"A diagram showing step-by-step execution of code leading to a circular dependency error.\" /> <figcaption><a href=\"https://www.bryanbraun.com/assets/images/circular-dependencies.png\" target=\"_blank\">Click to view a larger version of this image</a>.</figcaption></figure><p>Here’s what we see on each step:</p><p><strong>Step 1:</strong> On line 1 of <code class=\"language-plaintext highlighter-rouge\">index.js</code>, execution pauses to download <code class=\"language-plaintext highlighter-rouge\">a.js</code> so its value <code class=\"language-plaintext highlighter-rouge\">a</code> can be imported.</p><p><strong>Step 2:</strong> Upon downloading <code class=\"language-plaintext highlighter-rouge\">a.js</code>, execution continues in <code class=\"language-plaintext highlighter-rouge\">a.js</code> but pauses on line 1 to download <code class=\"language-plaintext highlighter-rouge\">b.js</code>, so its value <code class=\"language-plaintext highlighter-rouge\">b</code> can be imported.</p><p><strong>Step 3:</strong> Upon downloading <code class=\"language-plaintext highlighter-rouge\">b.js</code>, execution continues in <code class=\"language-plaintext highlighter-rouge\">b.js</code> and finds an import on line 1 pointing back at <code class=\"language-plaintext highlighter-rouge\">a.js</code> (a circular import).</p><p><strong>Step 4:</strong> <code class=\"language-plaintext highlighter-rouge\">a.js</code> is already downloaded, but it has no exports defined because we haven’t executed anything past line 1 in <code class=\"language-plaintext highlighter-rouge\">a.js</code> at this point. Thus, we cannot fulfill the import in <code class=\"language-plaintext highlighter-rouge\">b.js</code>.</p><p><strong>Step 5:</strong> Execution continues in <code class=\"language-plaintext highlighter-rouge\">b.js</code> with <code class=\"language-plaintext highlighter-rouge\">a</code> remaining uninitialized. When <code class=\"language-plaintext highlighter-rouge\">a</code> is called on line 3, the program errors with: <code class=\"language-plaintext highlighter-rouge\">ReferenceError: Cannot access 'a' before initialization</code>.</p><p>To summarize, the circular dependency results in code being executed with uninitialized values. This could result in various errors, like the <code class=\"language-plaintext highlighter-rouge\">ReferenceError</code> above.</p><h2 id=\"why-do-circular-dependencies-sometimes-not-cause-errors\">Why do circular dependencies sometimes not cause errors?</h2><p>JavaScript’s imports are <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import\">described as “Live Bindings.”</a> This means that the imported value can start out uninitialized (due to circular dependencies) and become fully useable once the rest of code has been evaluated. In other words, some circular dependencies are error-free because they “work themselves out” before you call the affected code.</p><p>I once worked in a code-base that was chock full of circular imports but none of them ever caused any issues. Why?</p><p>It’s because all the code was defined in functions, none of which would be called until after everything was loaded.</p><p>To demonstrate, we can update the last scenario to work in a similar way:</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/circular-dependencies-2.png\" alt=\"A diagram showing step-by-step execution circular dependency code without any errors.\" /> <figcaption><a href=\"https://www.bryanbraun.com/assets/images/circular-dependencies-2.png\" target=\"_blank\">Click to view a larger version of this image</a>.</figcaption></figure><p>Steps 1-4 are the same as above but things start to change at step 5:</p><p><strong>Step 5:</strong> <code class=\"language-plaintext highlighter-rouge\">a</code> remains uninitialized but instead of being called directly, it is placed in a function definition (no error).</p><p><strong>Step 6:</strong> With <code class=\"language-plaintext highlighter-rouge\">b.js</code> completed, execution in <code class=\"language-plaintext highlighter-rouge\">a.js</code> continues down to line 3, which defines the export for <code class=\"language-plaintext highlighter-rouge\">a</code>. From this point on, any code calling <code class=\"language-plaintext highlighter-rouge\">a</code> will get an initialized value, as a result of the live bindings.</p><p><strong>Step 7:</strong> We call <code class=\"language-plaintext highlighter-rouge\">a()</code> successfully, which in-turn calls <code class=\"language-plaintext highlighter-rouge\">b()</code>. Ultimately, all the code gets called with no errors.</p><p>To summarize, by the time we actually call that “uninitialized a”, the live bindings have updated its value and it’s no longer uninitialized. We’re safe because the value of <code class=\"language-plaintext highlighter-rouge\">a</code> is only retrieved when the variable is actually used.</p><p>Now, I wouldn’t recommend this as a way of fixing dependency issues. I’d feel better about removing the circular dependencies altogether. Still, I’ll bet there are plenty of production apps with circular dependencies that currently rely on this behavior.</p><h2 id=\"preventing-circular-dependencies\">Preventing circular dependencies</h2><p>While JavaScript may not have built-in circular dependency checking, we still have options for preventing these issues.</p><p>3rd-party tools like <a href=\"https://github.com/pahen/madge\">madge</a> and <a href=\"https://www.npmjs.com/package/eslint-plugin-import\">eslint-plugin-import</a> can perform static analysis on your JavaScript codebase and detect circular dependencies before they become unwieldy. Some monorepo tools like NX and Rush have similar features built-in to their workflows.</p><p>Of course, the best prevention is a well-organized codebase, with a clear hierarchy for shared code.</p><h2 id=\"what-about-node--bun--webpack--etc\">What about Node / Bun / Webpack / etc?</h2><p>The examples I shared above are focused on the “ES modules in the browser” use-case, but JavaScript runs in a lot of different contexts. Server-side JavaScript doesn’t need to download its source over the network (making it more like Python) and bundling tools like Webpack can combine all the code into a single file. Are circular dependencies an issue in these scenarios?</p><p>In short, yes. In my experiments, I was surprised to find that the error outcomes for browser, server, and bundler were basically the same.</p><p>For example, with Webpack, the <code class=\"language-plaintext highlighter-rouge\">import</code> statements were removed but the combined code still produced the same error:</p><div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">// b.js</span><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">b.js:</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">a</span><span class=\"p\">);</span> <span class=\"c1\">// ReferenceError: Cannot access 'a' before initialization</span><span class=\"kd\">const</span> <span class=\"nx\">b</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">B</span><span class=\"dl\">'</span><span class=\"p\">;</span><span class=\"c1\">// a.js</span><span class=\"nx\">console</span><span class=\"p\">.</span><span class=\"nx\">log</span><span class=\"p\">(</span><span class=\"dl\">'</span><span class=\"s1\">a.js:</span><span class=\"dl\">'</span><span class=\"p\">,</span> <span class=\"nx\">b</span><span class=\"p\">);</span><span class=\"kd\">const</span> <span class=\"nx\">a</span> <span class=\"o\">=</span> <span class=\"dl\">'</span><span class=\"s1\">A</span><span class=\"dl\">'</span><span class=\"p\">;</span></code></pre></div></div><p>I should also mention that while Node.js produced the same error when using the <code class=\"language-plaintext highlighter-rouge\">import</code> syntax (ESM), it behaved differently when using the <code class=\"language-plaintext highlighter-rouge\">require</code> syntax (CommonJS):</p><figure> <pre>$ node node-entry.cjs(node:13010) Warning: Accessing non-existent property 'Symbol(nodejs.util.inspect.custom)' of module exports inside circular dependency(Use `node --trace-warnings ...` to show where the warning was created)(node:13010) Warning: Accessing non-existent property 'constructor' of module exports inside circular dependency(node:13010) Warning: Accessing non-existent property 'Symbol(Symbol.toStringTag)' of module exports inside circular dependency</pre> <figcaption>It's nice that the warnings say \"circular dependency\" explicitly, when using CommonJS.</figcaption></figure><p>This makes sense when you consider that CommonJS is an entirely different import system that doesn’t conform to <a href=\"https://tc39.es/ecma262/#sec-modules\">the ECMAScript Modules spec</a>. Comparing the two is comparing apples and oranges!</p><h2 id=\"conclusion\">Conclusion</h2><p>Circular dependencies can be confusing but it makes a lot more sense when you walk through the scenarios step by step. As always, nothing beats an experiment for getting a clear understanding of something like this.</p><p>If you want a closer look at my test results, feel free to check out <a href=\"https://github.com/bryanbraun/circular\">the repo</a>.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/03/29/breaking-down-circular-dependencies-javascript/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/03/28/technology-you-dont-have-to-think-about", "url": "https://www.bryanbraun.com/2025/03/28/technology-you-dont-have-to-think-about/", "date_published": "2025-03-28T00:00:00-04:00", "date_modified": "2025-03-28T00:00:00-04:00", "title": "Technology you don't have to think about", "summary": " “Civilization advances by extending the number of important operations which we can perform without thinking about them.” – Alfred North Whitehead", "content_html": "<blockquote> <p>“Civilization advances by extending the number of important operations which we can perform without thinking about them.”</p> <p>– <a href=\"https://www.brainyquote.com/quotes/alfred_north_whitehead_108058\">Alfred North Whitehead</a></p></blockquote><p>Effective technology takes our most time-consuming tasks and moves them into the background so we can focus on other important things. We can see this happening on the civilization level with the infrastructure we’ve built to get inexpensive food, water, energy, and transportation.</p><p>But it also works on the personal level. Whether it’s a high-quality dishwasher or well-designed personal-finance software, technology is our primary tool for saving time and redirecting our attention.</p><p>But not all technology is successful at this. The power of technology is abstraction and <a href=\"https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction\">a poor abstraction</a> is worse than having no technology at all. Case in point: a smart light-bulb that never saves you enough time to recover the time you spent setting it up.</p><p>Simon Sarris has <a href=\"https://map.simonsarris.com/p/careful-technology\">a great post</a> describing some of these issues:</p><blockquote> <p>“Many modern devices (and apps) really excel at squishing tradeoffs into weird shapes. They are better thought of as little imps that sneak into homes and ask for more and more of your attention. They want to gently claw at your eyes and ears. They want to put notifications on your phone and remind you that you need to interact with them, or buy more of them, so that they might become even more convenient.”</p> <p>Simon Sarris, <a href=\"https://map.simonsarris.com/p/careful-technology\">Careful Technology</a></p></blockquote><p>Much of our technology has small hidden costs. A new app on your phone, an audible hum in the background, a recommended cleaning every six months, recurring manual software updates, monthly emails in your inbox, increased risk of a breaker trip, reduced counter-top space, parts that need replaced, a small monthly fee, a new username and password, batteries to recharge, parts to recycle, etc, etc.</p><p>Each cost seems small but with enough bad technology you face death by a thousand cuts. You lose time, peace, and other more difficult-to-quantify things, like “<a href=\"https://open.substack.com/pub/simonsarris/p/careful-technology?selection=d9528cb6-b6ad-4eec-81e1-857ec4bf0457\">the cozier feeling of home</a>.”</p><p>We have to be discerning about the technologies we let into our lives. I’m tired of technology that trades one set of problems for another. I want that technology you don’t have to think about.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/03/28/technology-you-dont-have-to-think-about/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/02/17/you-cant-fix-a-problem-you-dont-understand", "url": "https://www.bryanbraun.com/2025/02/17/you-cant-fix-a-problem-you-dont-understand/", "date_published": "2025-02-17T00:00:00-05:00", "date_modified": "2025-02-17T00:00:00-05:00", "title": "You can't fix a problem you don't understand", "summary": "A few weeks ago, I was building a server-side API client. I had written the code and tested it in isolation. Everything looked good. Unfortunately, when I included it in the main service, I started seeing errors.", "content_html": "<p>A few weeks ago, I was building a server-side API client. I had written the code and tested it in isolation. Everything looked good. Unfortunately, when I included it in the main service, I started seeing errors.</p><p>I decided to try asking an AI tool for suggestions. I gave it the error message and a bunch of context. It gave me a solution with a detailed explanation. The errors went away.</p><p>But the solution didn’t sit right with me. It was a bit complex, introducing more layers of code and various protections. The errors were gone, but I couldn’t clearly explain <em>why</em> it worked, and that was bothering me.</p><p>While the code was being reviewed, I decided to take another look. I brought back the error and spent some time digging into the stack trace. That’s when I made the discovery: it was an environment issue. All I needed to do was set an environment variable and the issue would be fixed. The AI-provided code had been masking the <em>real</em> issue, quietly suppressing the error, and hiding the truth in its complexity.</p><p>Now this is the part of the post where I’m supposed to criticize AI programming tools. I won’t be doing that. This isn’t an AI problem.</p><p>I remember the first time I tried to fix a memory leak. Certain iPhone users would load the webpage, interact for a while, and then randomly the webpage would crash. We struggled to diagnose the issue (Safari’s devtools weren’t great at the time). We <em>thought</em> we fixed it several times but the issue kept coming back.</p><p>Why? Because we didn’t understand the problem.</p><p>We kept digging and eventually we found it: one of our dependencies was storing massive amounts of data on the <code class=\"language-plaintext highlighter-rouge\">window</code> object and it wasn’t getting cleaned up. We added a cleanup step and the problem was gone for good.</p><p>Since then, I’ve adopted a mantra: <strong>you can’t fix a problem you don’t understand</strong>.</p><p>It doesn’t matter if the “fix” comes from AI, Stack Overflow, or trial-and-error. If you don’t know <em>why</em> it works, there’s a good chance that it doesn’t.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/02/17/you-cant-fix-a-problem-you-dont-understand/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/01/01/made-in-2024", "url": "https://www.bryanbraun.com/2025/01/01/made-in-2024/", "date_published": "2025-01-01T00:00:00-05:00", "date_modified": "2025-01-01T00:00:00-05:00", "title": "Made in 2024", "summary": "Here are some things I made in 2024:", "content_html": "<p>Here are some things I made in 2024:</p><ul> <li><a href=\"https://musicbox.fun\">Music Box Fun</a>: Advanced Editing (a new major feature): <ul> <li>Adds multiple-note selection for bulk operations on notes (like deletion, copy/paste, nudging and dragging)</li> <li>Also includes a “space editor” for arbitrarily adding/removing space anywhere in the song</li> <li>Includes other niceties like note highligting during playback and pitch highlight on hover</li> </ul> </li> <li>Music Box Fun songs I made: <ul> <li>Elliott’s Theme (Stardew Valley) in <a href=\"https://musicbox.fun/#1XQAAAAIEAQAAAAAAAABBqEgtkQkmc5J-d-O8MoS8uJWWjdOVVp6RR2f76S-zuNpEN9llG36IX36hWyvBWZtWu6EZzyxeO7YpsIn6IzOtR6JfpUxr2GcVsGEr8Ai80vhdXIvuJ4g0dkskuyVkcVuM2WCPuG7rtABABhw1rncetDt9CUjKo_oD0AB9-N-hXA-SrM4ZloIYQ5OWVkbimnW5-hRcUQWBDpp18mUj2Fz-B0Vy8TG12KzhTqoDl5w64Tg_eKjIQ8I3kL8lFc5JTPzfBGY3c5HHlwmgFmtms0l8P-cspYNpef_djqAA\">15-note</a> and <a href=\"https://musicbox.fun/#1XQAAAAJEAQAAAAAAAABBqEgtkWPmc5J-d-O8MoS8uJWWjdOVVp6RR2f76S-zuNpEN9llG15AfJQIAA3xo1iAikH6EDcgr3U4peOuMiSVI9haTk9HuRfvOYz9ucipvnBrte6xDsM9FpQ3afYcX0UG0H9HFXF_x_CrkZmSXbwWKzjXCj7hA6jLruNK1xkdLssYz3LSdwb3NlkmRn3EDi05jxh-24Pl9gYj46okApF7qPdfcE8ZIEF0QFYYVp8C6B3JDMLtRhP6CuJtcUVRRSnBQHZuwPYiHR6WQFrp47aiHJQlX-4wLvtUSsVFqT57WGq8WFFy1GWZ41kIW6-htyd44vZpWlTGCqiep6v0xjnVruSe5lYFB_5DIdXBjddwOf9yLGAA\">30-note</a> versions</li> <li><a href=\"https://musicbox.fun/#1XQAAAAJ7AgAAAAAAAABBqEgrAiJVxwoEI9-yQpJai6RWkkf8pdkQ1VFJGnhN6pstpbPSCFXnBv20Q5nLps7pCBtw1lBmvj3YWkFk0S93R-A7Siif-DJeKu_PhMOuiD_qdtW801zD4WV21MWPrFwxqSrWDfHU9mD2FCVBOE36jXoACfq7gIzgKOTZbst3Kc5HCzx7dikHuvuKjJjr3v_cTlpYYSJiGtCXfxgqpuAWAXxSzdBSB5FhKhEdQiQEGLtZTwMuPcCWEx1oJ7Ylng7gxKDrkjqiAMCUzvV4AvujTQiOoZ8JkPiOj4Gb5Ia4UgwUVp1-uyF_fZj7K-0RjQTC0-cyWeKHAF_7XX48VU_DxpLPN4l4CsY42P8T2fFZqFr-CQ2JayrisiupePxjiT9CV4_MH-4K26uKYocTeuVInjgy7QLUNr6BFDvNrHtPhOocpj22mekGR-DqVFy-p2XrB3sRmTqvOhXR_CSddjhhHr1hJ3VsH_u4O8qgkuQk67AOI8a_Kgq5bdzwaAJTWCH9UDk8gVJ4aPlVAX-qKFsnmtjtyES1QX635YAgBp_QmIwfkwPA8Q7OY6L_PSKuAA\">Bluey Theme Song</a></li> <li><a href=\"https://musicbox.fun/#1XQAAAAKXAgAAAAAAAABBqEgrsmZ7LPyci4tWRLMFoBCXHljZGctyNY6EHqVCXQQLKMlNuABNdQ39ZBCxNwKGAM5OON7ahoUV_ykvr9cTfJ-_JZt0_432oUiI9vuc8A8PFDpJfREMFKjykA9q6TOpupu3DRQC1fpd5ylYM8M4Kqg80y4aoyksmXg0vmjsTA9mFQC_QYfgSR_B7dDfuVj8EhIntGmN1inl8uVBML8fZyVrpShVpt8HDuOoDKDRE9_CpR2Q_97BuN9usZqfLT0GrkR2k3-2Svr4dBqdiVfCHClmTYe4t_aVFaSZi22rYvXDBHGDC3EcpWsuruQuAZ4jyZcr8rxkG5xVkLgVHU_hOZnz44WV085uIyX6TgZF8ti5aP9JsT_R3OIIWNLTtW35LUvDnyceNYeP7mpJ1IaRem9ycXRlqOlT7evT__L035pqpL4atRdpznd76ESN0VB6m8Vtw1AkKT3Qutp3eWDwHLeetsd4O5Rpe3neLhO1HLpAHVUB08A1bkbHh_r8Kkv1vtNen_QKC1GP7bIXBNdmZaNewXnifQJw1hzl5ZYhvBvNCU6YDTVehEIchJq0r1UbCImW1YwHlYQGo9L_aco2AA\">Jupiter Theme (The Planets)</a></li> </ul> </li> <li>11 new projects added to Let’s Get Creative, now available at a new domain name: <a href=\"https://letsgetcreative.today\">https://letsgetcreative.today</a></li> <li><a href=\"https://www.youtube.com/watch?v=lF6-P3UnHSE\">The Firefly Building in Minecraft</a> (if you know, you know)</li> <li><a href=\"https://www.bryanbraun.com/blog/#y2024\">13 blog posts on bryanbraun.com</a>, including <a href=\"https://github.com/bryanbraun/unusual-commit-ids\">a companion repo</a> to <a href=\"https://bryanbraun.com/2024/11/19/unusual-git-ids/\">my post on unusual Git IDs</a>.</li></ul><p>I’m happy with this list. It was a year of many challenges. In February we got hit by a flooded basement and an emergency hospitalization which left my wife with limited mobility for a month. It was a difficult time and I’ll be forever grateful for the family and friends who helped us get through it. It was also a year of growth for my kids in particular, bringing many new parenting challenges.</p><p>At the same time, I have so much to be grateful for. My wife and I hit our fifteenth anniversary and our relationship has grown stronger despite (or perhaps because of) the storms we’ve weathered. Every year brings new opportunities and challenges and it’s a privilege to have a committed partner that I can face them with. 🚵🏻♀️🚵🏻♂️</p><p>I wish you all the very best in 2025.</p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2025/01/01/made-in-2024/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2024/12/03/links-11", "url": "https://www.bryanbraun.com/2024/12/03/links-11/", "date_published": "2024-12-03T00:00:00-05:00", "date_modified": "2024-12-03T00:00:00-05:00", "title": "Links #11", "summary": "Here are some more links to things that I keep thinking about. I shared a bit more detail on these ones than I usually do—there were so many good quotes to include. Enjoy!", "content_html": "<p>Here are some more links to things that I keep thinking about. I shared a bit more detail on these ones than I usually do—there were so many good quotes to include. Enjoy!</p><hr /><p><strong><a href=\"https://freddiedeboer.substack.com/p/selfishness-and-therapy-culture\">Selfishness & Therapy Culture</a></strong></p><p>Earlier this year, there was <a href=\"https://www.nytimes.com/2024/06/27/well/mind/forgiveness-healing-peace.html\">a post in the New York Times called “Sometimes, Forgiveness is Overrated”</a>. Selfishness & Therapy Culture is a brilliant critique, both of the NYT post and of the culture that produced it.</p><blockquote> <p>“Who in the world could possibly look out at contemporary society and think that the message ‘put yourself before other people’ isn’t loud enough? Every women’s site on the internet preaches this message. Every hustle bro on Threads preaches this message. Every therapist between San Diego and Sacramento preaches this message. Every eight-word meme in overly elaborate cursive font on Pinterest preaches this message. … There’s the girlboss version and the Joe Rogan bro version and horoscope obsessive version and the Wall Street grindset version and the fitness guru on trenbolone version…. Justification for selfishness is not in short supply. It is the water in which we swim.”</p></blockquote><p>My opinion: the celebration of selfishness is a form of societal sickness that we have, and it isn’t new. Faith practices that center on forgiveness and selflessness are part of the social infrastructure our forefathers erected to fight the sickness, and when we erode them, we do so at our own peril.</p><p><strong><a href=\"https://www.notboring.co/p/the-morality-of-having-kids-in-a\">The Morality of Having Kids in a Magical, Maybe Simulated World</a></strong></p><p>This post is a reaction to the recurring sentiment that <a href=\"https://www.newyorker.com/magazine/2023/11/27/the-quickening-elizabeth-rush-book-review-the-parenthood-dilemma-gina-rushton\">perhaps it is immoral to have children in a world threatened by climate change</a>. The “evidence for a simulated world” part is a bit farfetched, but what interested me the most was the idea of framing human progress in the terms of “ENERGY: The Game.” It gave me this feeling like, oh, we’re all collectively playing this game<sup><a href=\"#footnotes\">1</a></sup>, and it’s going somewhere, so what is my role? Instead of playing for myself, or my family, or my country, I could be playing for humanity, in its fight against <a href=\"https://www.noahpinion.blog/p/the-elemental-foe\">the elemental foe</a>. In this game, is there anything more noble then working to push technology forward, creating value out of nothingness, helping to create abundance for all humanity?</p><p><strong><a href=\"https://caseyhandmer.wordpress.com/2023/08/25/you-should-be-working-on-hardware/\">You should be working on Hardware</a></strong></p><p>I work in software but I studied mechanical engineering, so I have a deep respect for those who work in hardware (like Casey Handmer, the author). I love how this post calls out the needs in the world and <a href=\"https://marginalrevolution.com/marginalrevolution/2018/10/high-return-activity-raising-others-aspirations.html\">raises your aspirations</a> to work on them:</p><blockquote> <p>“You only get a few chances to work on really big projects, to build the future, to move humanity forward.”</p></blockquote><blockquote> <p>“Most hardware concepts will never even be dreamed, let alone designed, built, and brought to market. There are many, many more important, good businesses to build than there are people building them. When you build something, you can accelerate the future by decades!”</p></blockquote><p>The whole post is inspiring, not to mention the follow-up post <a href=\"https://caseyhandmer.wordpress.com/2024/06/08/how-to-learn-hardware/\">How to learn Hardware</a>, which included this wisdom:</p><blockquote> <p>“It is always easier to learn things you enjoy doing. The art lies in finding ways to enjoy the things that are necessary…. and finding ways to avoid enjoying to excess things that are counterproductive to your mission in life.”</p></blockquote><p><strong><a href=\"https://caseyhandmer.wordpress.com/2024/01/30/the-well-rounded-engineer/\">The Well-Rounded Engineer</a></strong></p><p>Another one from Casey Handmer (I’ve been reading through his archives). In this one, he explains that all nontrivial engineering efforts eventually encounter “the coordination problem.” It is very common for engineers to be oblivious of it, which manifests itself in things like complaints about management being inept, or resentment that their work was “thrown away.” Here, Casey argues that elite engineers should understand the coordination problem, what causes it, and ways to resolve it. I’ve historically avoided the management track because I enjoy creating things and people are messy. That said, management exists to help solve the coordination problem and this post helped me see that I ought to develop a working understanding of management if I ever want to create something nontrivial (and I do!). Casey’s blog has quickly become one of my favorites.</p><p><strong><a href=\"https://aethermug.com/posts/you-don-t-have-time\">You Don’t Have Time to Read Books That Won’t Change Your Life</a></strong></p><p>We’ve all read a book that changed our life, right? Given that there’s 50 million+ books in existence, there are likely hundreds of thousands of other life-changing books out there that we only haven’t read because we haven’t found them yet. This post pushed me to imagine what my life might look like if I read twice as many “life-changing books” as I currently do, and how I might do that. I’ve been on a reading kick recently (a book a week for the past six weeks!) and I feel like it’s added a richness to my life that I don’t get from podcasts or music.</p><hr class=\"section-divider\" /><p id=\"footnotes\"> <small><sup>1</sup> <em>\"It's like we got handed a save file of a game, where others put in millions of hours of work, and we can decide what game we want to play in the future.\" - <a href=\"https://www.youtube.com/watch?v=c1nYtX-NUsc&t=426s\" target=\"_blank\">Kurzgesagt</a></em></small></p><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2024/12/03/links-11/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2024/11/19/unusual-git-ids", "url": "https://www.bryanbraun.com/2024/11/19/unusual-git-ids/", "date_published": "2024-11-19T00:00:00-05:00", "date_modified": "2024-11-19T00:00:00-05:00", "title": "Unusual Git IDs", "summary": "You can search Github for unusual commit IDs:", "content_html": "<p>You can search Github for unusual commit IDs:</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/github-commit-id-search.png\" alt=\"Github search page including a search for commits with an ID of 0000000\" /> <figcaption>There are over 2k commits starting with 0000000!</figcaption></figure><p>While looking for commits like these, I started to become suspicious that people were intentionally modifying their commits IDs to be unusual:</p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/eeeeeee.png\" alt=\"Github search result for a commits with and ID of 'eeeeeee'\" /> <figcaption>A commit ID of <code>eeeeeee</code>, with a message of \"eeeeeee\", created by a user named \"eeee\".</figcaption></figure><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/defaced.png\" alt=\"List of consecutive commits, all with an ID of 'defaced'\" /> <figcaption>What are the odds that this user happened to commit seven consecutive commits, all with an ID of \"defaced\"?</figcaption></figure><p>Sure enough, there are tools you can use to generate “vanity” commit IDs (like <a href=\"https://github.com/prasmussen/git-vanity-hash\">git-vanity-hash</a>). These tools work by using the “brute-force” method. Basically, they create a commit, check the hash, and if it doesn’t match, they increment a piece of data in the commit header and try again.</p><p>That’s pretty clever but I’m more interested in unusual commit IDs that occur naturally. How often do they happen? Have I ever created one before?</p><h2 id=\"searching-my-commits\">Searching my commits</h2><p>I decided to use Github’s CLI tool, <code class=\"language-plaintext highlighter-rouge\">gh</code>, to query the Github API and pull down a list of my public commits:</p><div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>gh search commits <span class=\"nt\">--author</span> bryanbraun <span class=\"nt\">--json</span> commit <span class=\"nt\">--jq</span> <span class=\"s2\">\".[].commit.tree.sha\"</span> <span class=\"nt\">--limit</span> 1000</code></pre></div></div><p>This worked but <a href=\"https://github.com/cli/cli/discussions/7010\">it has a maximum limit of 1000 commits</a> (my public total is closer to 4k). Eventually, I found a way to get them all by writing a bash script to break up my queries by year, generating one file per query, and combining the files when done:</p><div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">#!/bin/bash</span><span class=\"c\"># To prevent API rate limits, set a personal access token to the GITHUB_TOKEN</span><span class=\"c\"># environment vairable in your terminal before running this script.</span><span class=\"nv\">username</span><span class=\"o\">=</span><span class=\"s2\">\"bryanbraun\"</span><span class=\"nv\">start_year</span><span class=\"o\">=</span>2012<span class=\"nv\">end_year</span><span class=\"o\">=</span>2024<span class=\"c\"># Create a file for each year</span><span class=\"k\">for </span>year <span class=\"k\">in</span> <span class=\"si\">$(</span><span class=\"nb\">seq</span> <span class=\"nv\">$start_year</span> <span class=\"nv\">$end_year</span><span class=\"si\">)</span><span class=\"p\">;</span> <span class=\"k\">do </span><span class=\"nv\">next_year</span><span class=\"o\">=</span><span class=\"k\">$((</span>year <span class=\"o\">+</span> <span class=\"m\">1</span><span class=\"k\">))</span> <span class=\"nb\">echo</span> <span class=\"s2\">\"Fetching commits from </span><span class=\"nv\">$year</span><span class=\"s2\"> to </span><span class=\"nv\">$next_year</span><span class=\"s2\">...\"</span> gh api <span class=\"nt\">-X</span> GET <span class=\"se\">\\</span> <span class=\"s2\">\"/search/commits?q=author:</span><span class=\"nv\">$username</span><span class=\"s2\">+committer-date:</span><span class=\"nv\">$year</span><span class=\"s2\">-01-01..</span><span class=\"nv\">$next_year</span><span class=\"s2\">-01-01\"</span> <span class=\"se\">\\</span> <span class=\"nt\">--header</span> <span class=\"s2\">\"Accept: application/vnd.github.cloak-preview\"</span> <span class=\"se\">\\</span> <span class=\"nt\">--paginate</span> <span class=\"se\">\\</span> <span class=\"nt\">-q</span> <span class=\"s1\">'.items.[].sha'</span> <span class=\"o\">>></span> <span class=\"s2\">\"commits-</span><span class=\"nv\">$year</span><span class=\"s2\">.txt\"</span><span class=\"k\">done</span><span class=\"c\"># Combine all files into one</span><span class=\"nb\">cat </span>commits-<span class=\"k\">*</span>.txt <span class=\"o\">></span> all_commits.txt<span class=\"c\"># Remove unused files</span><span class=\"k\">for </span>year <span class=\"k\">in</span> <span class=\"si\">$(</span><span class=\"nb\">seq</span> <span class=\"nv\">$start_year</span> <span class=\"nv\">$end_year</span><span class=\"si\">)</span><span class=\"p\">;</span> <span class=\"k\">do </span><span class=\"nb\">rm</span> <span class=\"s2\">\"commits-</span><span class=\"nv\">$year</span><span class=\"s2\">.txt\"</span><span class=\"k\">done</span></code></pre></div></div><p>The result is a 4000-line file with one commit ID per line:</p><p><img src=\"https://www.bryanbraun.com/assets/images/all-commits.png\" alt=\"all commits\" /></p><p>Now I just needed to scan my commit IDs for unusual patterns. Grep is good at this kind of thing so I put together a bash script that uses grep to do various checks:</p><div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">#!/bin/bash</span><span class=\"c\"># Define the input file containing commit SHAs</span><span class=\"nv\">input_file</span><span class=\"o\">=</span><span class=\"s2\">\"all_commits.txt\"</span><span class=\"nb\">echo</span> <span class=\"s2\">\"Analyzing patterns in the first 7 characters of commit SHAs...\"</span><span class=\"c\"># Check 1: SHAs where the first 7 characters are the same</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">1. SHAs where the first 7 characters are the same:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^(.)\\1{6}'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span><span class=\"c\"># Check 2: SHAs starting with a palindrome (e.g., \"abcdcba\")</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">2. SHAs starting with a palindrome:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^(.)(.)(.)(.)\\3\\2\\1'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span><span class=\"c\"># Check 3: SHAs where the first 7 characters form an ascending sequence</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">4. SHAs where the first 7 characters form an ascending sequence:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^(0123456|1234567|2345678|3456789|456789a|56789ab|6789abc|789abcd|89abcde|9abcdef)'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span><span class=\"c\"># Check 4: SHAs where the first 7 characters form a descending sequence</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">5. SHAs where the first 7 characters form a descending sequence:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^(fedcba9|edcba98|dcba987|cba9876|ba98765|a987654|9876543|8765432|7654321|6543210)'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span><span class=\"c\"># Check 5: SHAs where the first 7 characters form a repeating pattern (e.g., \"abababa\")</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">6. SHAs where the first 7 characters form a repeating pattern:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^(.)(.)\\1\\2\\1\\2\\1'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span><span class=\"c\"># Check 6: SHAs where the first 7 characters are vowels only</span><span class=\"nb\">echo</span> <span class=\"s2\">\"</span><span class=\"se\">\\n</span><span class=\"s2\">7. SHAs where the first 7 characters are vowels only:\"</span><span class=\"nb\">grep</span> <span class=\"nt\">-E</span> <span class=\"s1\">'^[aeiouAEIOU]{7}'</span> <span class=\"s2\">\"</span><span class=\"nv\">$input_file</span><span class=\"s2\">\"</span></code></pre></div></div><p>I ran this and got something! One of my commits was a palindrome: <code>c5a7a5c</code></p><figure class=\"center\"> <img src=\"https://www.bryanbraun.com/assets/images/my-palindrome-commit.png\" alt=\"Github search page including a commit with an ID of c5a7a5c\" /> <figcaption><code>c5a7a5c</code>, my most unusual commit ID, was created over ten years ago!</figcaption></figure><p>It’s not the most beautiful commit ID but I’m pretty happy that I found something.</p><h2 id=\"what-else-could-we-do\">What else could we do?</h2><p>My list of checks was pretty short, so we could expand it if we wanted. Here are some ideas:</p><ol> <li>Check if the first seven characters are all letters, or all numbers (e.g., “afddcbf”, “2950312”).</li> <li>Check if the first seven characters are composed of only two distinct characters (e.g., “a44aaa4”).</li> <li>Check commit IDs against <a href=\"https://nedbatchelder.com/text/hexwords.html\">a list of words constructed from hexadecimal characters</a> (e.g., “baff1ed”).</li> <li>Check if the first seven characters are in alphabetical order (e.g., “accdfff”).</li> <li>Check if the first seven characters are in numerical order (e.g., “0035789”).</li> <li>Check for interesting patterns outside of the first 7 characters of the commit ID.</li></ol><p>I actually tried that first idea but I found that the “all numbers” case was too common (it found over a dozen matches in my commit history). You gotta strike the right balance.</p><p>It would be fun to put together a web interface where people could put in their Github username and scan their own commits, but I’ve come to terms with the fact that there’s not enough time in the world to build all the interesting little websites that ought to exist.</p><p>I used ChatGPT to help me build the scripts, which made it possible to punch out this analysis in a single afternoon. If it took any longer, I likely wouldn’t have attempted it. This is a perfect example of <a href=\"https://simonwillison.net/2023/Mar/27/ai-enhanced-development/\">AI-enhanced development making me more ambitious with my projects</a>.</p><p>I put all the scripts in a public repo at <a href=\"https://github.com/bryanbraun/unusual-commit-ids\">bryanbraun/unusual-commit-ids</a>. Feel free to fork it, download it, and scan your own commits. If you find any unusual commits in your own history, you should share them in the comments (naturally occurring commits only)!</p><p>Finally, if you thought this was interesting, you may like my other git commit-related projects:</p><ul> <li><a href=\"https://github.com/sparkbox/commit-colors\">Commit colors</a></li> <li><a href=\"https://x.com/CommitHaikus\">Commit haikus (a now-retired twitter bot)</a></li></ul><hr /><p>Thanks for reading in your feed reader! Let me know your thoughts by leaving a comment on <a href=\"https://www.bryanbraun.com/2024/11/19/unusual-git-ids/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" } ] }