Check whether your feed is valid. For more information about JSON Feed, see the specification. Find the validator source code on GitHub.
GET validation response in JSON format.
{ "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/09/29/links-13", "url": "https://www.bryanbraun.com/2025/09/29/links-13/", "date_published": "2025-09-29T00:00:00-04:00", "date_modified": "2025-09-29T00:00:00-04:00", "title": "Links #13", "summary": "", "content_html": "<hr /><p><strong><a href=\"https://www.youtube.com/watch?v=FALlhXl6CmA\">Sean Evans interviews Conan OBrien on Hot Ones</a></strong></p><p>I don’t know that much about Conan OBrien, but I definitely wasn’t expecting this. Conan is obviously putting on a show, and the unhingedness of the whole thing is very entertaining, but there are also these moments of truth that speak to what’s really happening (in <a href=\"https://radiolab.org/podcast/montreal-screwjob\">a Montreal Screwjob kinda way</a>). Case-in-point:</p><blockquote> <p>“You can’t stop me from being who I am. I’ve gotta go for it. Whatever I do, I have to go a hundred and ten percent.”</p> <p>“I know, you’ve gotta commit to the bit.”</p> <p>“It’s not a bit. This is life! Don’t say ‘commit to the bit.’ This is life.”</p></blockquote><p>What we are actually watching here, is full and total commitment. The water buffalo, the drinking the hot sauce, the desperate screaming about wanting to stay relevant. Yes, the show is funny, but the commitment is no joke.</p><p><strong><a href=\"https://qz.com/humanoid-robot-olympics-2025-china-tesla-amazon\">China’s Robot Olympics</a></strong></p><p>This is great. It’s silly, but competitions like these drive people to innovate, and I’d love to see more innovation in robotics. I want to see more countries participate, including the US. If it gets bigger, I could actually see my self watching the competitions. There’s something endearing about watching a bunch of dorky robots stumble around like toddlers.</p><p><strong><a href=\"https://nealstephenson.substack.com/p/emerson-ai-and-the-force\">Emerson, AI, and The Force</a></strong></p><p>Neal Stephenson reflects on “The Primer,” an personalized education device that plays a big part in his 1995 book, The Diamond age. The Primer bears some resemblances to modern AI systems, but it’s remains to be seen whether modern AI can be as empowering as the Primer was in the story. My biggest takeaway: some things are difficult to teach (via AI), because they aren’t knowledge to be memorize or skills to be mastered. They are <em>“…a stance from which to address the world and all its challenges. The conviction that one has a fighting chance to overcome or circumvent whatever obstacles the world throws in one’s path. The way you acquire it is by trying, and sometimes failing, to do difficult things.”</em></p><p><strong><a href=\"https://www.youtube.com/@BrickTechnology\">Brick Technology</a> and <a href=\"https://www.youtube.com/@BrickExperimentChannel\">Brick Experiment Channel</a></strong></p><p>Two YouTube channels about solving intense engineering challenges with legos. The kids and I have enjoyed watching some of these videos together in the evenings. A few of my favorites include <a href=\"https://www.youtube.com/watch?v=jRn5waE0qfk\">this one about using legos to break steel</a> and <a href=\"https://www.youtube.com/watch?v=RsuD6iC-Foc\">the 200 Wheel Vehicle</a>.</p><p><strong><a href=\"https://overreacted.io/open-social/\">Open Social</a></strong></p><p>Dan Abramov writes convincingly on how social media would be much better if it was built around open protocols. To explain this, he compares Bluesky’s AT Protocol to the organization of the distributed web (domain names, web-hosts, and user-owned content). The benefits of protocols appeal to me, which is why I’ve recently <a href=\"https://www.bryanbraun.com/2023/11/27/rejecting-the-algorithm/\">focused my consumption on self-curated RSS</a>, but Dan believes that the AT Protocol can provide the benefits of RSS without sacrificing “truly social” features, like comments, likes, follows, etc. To me, the most compelling argument is that if users owned their social network, they could easily change services, which would force more competition between social media companies (and better competition might offer users options with less toxicity and addictiveness). I’d like to live in a world where I can follow and interact with select people on Twitter/X/Bluesky/Mastodon/etc, without subjecting myself to the worst parts of those communities. Time will tell whether AT Protocol will get adopted beyond Bluesky in a meaningful way (there’s also <a href=\"https://en.wikipedia.org/wiki/ActivityPub\">ActivityPub</a> and <a href=\"http://en.wikipedia.org/wiki/Nostr\">Nostr</a> competing for adoption in this space). I wish them well.</p><p><strong><a href=\"https://vscodethemes.com/\">VSCodeThemes.com</a></strong></p><p>I needed a new editor theme and this directory was helpful for finding one that was both beautiful and unambiguous for the colorblind (I went with <a href=\"https://vscodethemes.com/e/travis.simple-dark/simple-dark\">Simple Dark</a>).</p><p><strong><a href=\"https://time.com/7315003/russell-nelson-dignity-respect/\">Russell M. Nelson: We All Deserve Dignity and Respect</a></strong></p><p>Last week’s events included the <a href=\"https://www.nytimes.com/2025/09/28/obituaries/russell-m-nelson-dead.html\">death</a> of Russell M. Nelson, Prophet and President of the Church of Jesus Christ of Latter-day Saints at age 101. A few weeks ago, he wrote an editorial for Time.com (likely, his last public address), which included these important words:</p><blockquote> <p>“A century of experience has taught me this with certainty: anger never persuades, hostility never heals, and contention never leads to lasting solutions. Too much of today’s public discourse, especially online, fosters enmity instead of empathy. Imagine how different our world could be if more of us were peacemakers—building bridges of understanding rather than walls of prejudice—especially with those who may see the world differently than we do. I have seen bitter divisions soften when neighbors chose to listen to one another with respect rather than suspicion. Even small acts—like reaching out across lines of faith, culture, or politics—can open doors to healing. There is power in affording others the human dignity that all of God’s children deserve.”</p></blockquote><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/09/29/links-13/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "id": "/2025/09/28/some-thoughts-on-scaling-code-review", "url": "https://www.bryanbraun.com/2025/09/28/some-thoughts-on-scaling-code-review/", "date_published": "2025-09-28T00:00:00-04:00", "date_modified": "2025-09-28T00:00:00-04:00", "title": "Some thoughts on scaling code review", "summary": "AI code-writing tools make it possible for more people to write code than ever before. On my current team, I’ve seen PRs opened by engineers, designers, marketers, managers, and product people. There’s something special about empowering more people to build and fix things. That empowering feeling is a big reason I got involved in web-dev in the first place.", "content_html": "<p>AI code-writing tools make it possible for more people to write code than ever before. On my current team, I’ve seen PRs opened by engineers, designers, marketers, managers, and product people. There’s something special about empowering more people to build and fix things. That empowering feeling is a big reason I got involved in web-dev in the first place.</p><p>But we also have to care about codebase quality. AI-generated code isn’t always the best, and bad code has a tendency to multiply (especially as AIs use existing code patterns to suggest new code). Manual code review is effective, but I don’t want to spend all my time reviewing AI generated-code. AI scales code generation. How do we scale code review?</p><p>Here are some things I’ve seen work:</p><h2 id=\"strong-universal-linting-and-formatting-standards\">Strong, universal, linting and formatting standards</h2><p>Humans should not be checking for code formatting or linting issues. All major languages have tooling for this, so it’s one of the easiest things to set up and automate. I recommend running it on CI for all opened PRs, and having it run during local development (whether that be through watch tasks, pre-push hooks, or solid editor integration). I also recommend making your default rules pretty restrictive. It’s basically free consistency, so take advantage of it. Most tooling allows you to make one-off exceptions if needed (ideally, with a comment explaining <em>why</em> it’s needed).</p><h2 id=\"make-a-bunch-of-custom-linting-rules\">Make a bunch of custom linting rules</h2><p>Out-of-the-box linting can only get you so far. That’s where custom lint rules come in. Most modern linters support custom rules, and other tools go even further (see <a href=\"https://danger.systems/\">danger</a>, for example).</p><p>In my recent onboarding experience, I was super-impressed to find a library of custom lint rules, acting as living documentation of the patterns we were moving away from. Are you trying to migrate away from Moment.js? Write a custom lint rule for that. Are there special global objects that you don’t want to be abused? Write a custom lint rule for that. It’s not that hard to do (especially with a bit of AI help).</p><h2 id=\"a-process-for-burning-down-bad-patterns\">A process for burning-down bad patterns</h2><p>It’s not realistic to always have 100% compliance with your linting and coding standards. New patterns ought to be adopted as your needs change, and incremental adoption is usually less risky than doing it all at once. Ideally, your standards are clearly defined, and your codebase is always progressing towards them. How do we automate that? Here’s a few ideas:</p><p><strong>1: Have linters and formatters run only on changed files (instead of ALL files).</strong></p><p>This allows you to fail CI builds for new violations while ignoring violations in untouched files—great if you want to automate “leaving each file better than you found it.” In the JS world, you can do this with tools like <code class=\"language-plaintext highlighter-rouge\">pretty-quick</code> or <code class=\"language-plaintext highlighter-rouge\">eslint-plugin-diff</code>, but you shouldn’t <em>need</em> special packages. Most CLI linters can take an argument for a “files list”, and you can generate a list of changed files with git commands.</p><p><strong>2: When introducing a rule, create exceptions for all pre-existing violations, plus an explanatory comment.</strong></p><p>For example:</p><div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">// eslint-disable-next-line complexity - pre-existing violation, should be fixable</span><span class=\"kd\">function</span> <span class=\"nx\">processPayment</span><span class=\"p\">(</span><span class=\"nx\">user</span><span class=\"p\">,</span> <span class=\"nx\">order</span><span class=\"p\">)</span> <span class=\"p\">{</span><span class=\"p\">...</span></code></pre></div></div><p>This is great for codebases that already have universal linting and build failures for any violations. I like comments on linting exceptions because they help explain whether an exception is valid, or technical debt. You can <a href=\"https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/require-description.html\">make these comments required</a>.</p><p><strong>3: A racheting system</strong></p><p>A <a href=\"https://en.wikipedia.org/wiki/Ratchet_(device)\">rachet</a> allows motion in one direction but prevents motion in the other. As such, we can set up systems that only allow the number of bad patterns to decrease. For example, the JavaScript ecosystem includes tools like <a href=\"https://www.notion.com/blog/how-we-evolved-our-code-notions-ratcheting-system-using-custom-eslint-rules\">eslint-seatbelt</a>, <a href=\"https://github.com/phenomnomnominal/betterer\">betterer</a>, and <a href=\"https://github.com/diffjam/diffjam\">diffjam</a>, each of which uses a racheting process to gradually drive towards compliance.</p><h2 id=\"excellent-rules-for-ai-agents\">Excellent rules for AI agents</h2><p>An ounce of prevention is worth a pound of cure, and we can prevent bad code from being written in the first place with a set of excellent rules for AI agents. All the major coding agents support this (see <a href=\"https://www.anthropic.com/engineering/claude-code-best-practices#a-create-claudemd-files\">Claude</a>, <a href=\"https://cursor.com/docs/context/rules\">Cursor</a>, <a href=\"https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions\">Copilot</a>).</p><p>Well-written rules make a huge difference. When I joined ClassDojo, I could hardly believe how much “better” the coding AIs were. It wasn’t the AIs themselves (I was using the same models and editors on my own proejcts). It was the rules.</p><p>If you don’t have any rules in your project, create some (AI can help with this, but start small!). Then, anytime you get weird output from an AI, consider adding or adjusting the rules. Give engineers collective ownership over these rules with permission to adjust them as needed. This can be a powerful system for preventing undesirable patterns (especially ones outside the scope of linting).</p><h2 id=\"ai-code-review\">AI Code Review</h2><p>Use AI to do a first-pass code-review. I’ve seen this done with <a href=\"https://docs.cursor.com/en/bugbot\">Cursor’s bugbot</a> and the <a href=\"https://github.com/anthropics/claude-code-action\">Claude Code action</a>, but I’m sure that other tools and services exist. I don’t see these as a substitute for human review. They aren’t perfect, but I’ve found them to be good at calling out things you may have overlooked (unused code, hardcoded values, etc).</p><h2 id=\"conclusion\">Conclusion</h2><p>Any time you find yourself giving feedback, ask yourself if the feedback can be integrated into your systems. Over time, these systems should improve and the burden of code reviews can become progressively lighter. One reason senior engineers still matter is because they have the instincts and the agency to design and maintain these systems. They are the most qualified people to shepherd our codebases into a brave new world.</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/09/28/some-thoughts-on-scaling-code-review/\">the original post</a> or sending <a href=\"mailto:bbraun7@gmail.com\">me an email</a>.</p>" }, { "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, there’s a maintenance vs control 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 Minecraft 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 change</strong>.</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>" } ] }