More from Bryan Braun - Blog
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. Move on to ESM-only - 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 Checkboxland as ESM-only, in large part due to the influence of this post. My keyboard ergonomics journey as an engineer - 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! You Must Read at Least One Book To Ride - 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! Radiant - 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. Nobody Cares - 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? Precocious Young People Should Do Deep Technical Training - “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.” Of all the things I learned in the years I spent studying mechanical engineering, the most important was that I can understand anything… it’s just a matter of desire and time. 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. The Colors Of Her Coat - 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. LightNote - 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.
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. What are circular dependencies? Circular dependencies happen when your JavaScript import statements result in a loop: The loop can consist of two files, three files, or more. Any time your import statements create a loop like this, there’s a risk that your code won’t work as expected. How do you know when you have circular dependencies? There’s no easy way built into the language! In JavaScript, a circular dependency often manifests as a seemingly unrelated error (like a ReferenceError or TypeError). This is different from many other programming languages, which often tell you directly that your imports are bad: Python: ImportError Go: import cycle not allowed So why can’t JavaScript come out and say ⚠️ CircularDependencyError? It’s because JavaScript modules are designed to be loaded and executed on-the-fly. 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. 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. Stepping through a circular dependency in JavaScript The best way to explain the errors that JavaScript gives us is to step through a circular dependency scenario: Click to view a larger version of this image. Here’s what we see on each step: Step 1: On line 1 of index.js, execution pauses to download a.js so its value a can be imported. Step 2: Upon downloading a.js, execution continues in a.js but pauses on line 1 to download b.js, so its value b can be imported. Step 3: Upon downloading b.js, execution continues in b.js and finds an import on line 1 pointing back at a.js (a circular import). Step 4: a.js is already downloaded, but it has no exports defined because we haven’t executed anything past line 1 in a.js at this point. Thus, we cannot fulfill the import in b.js. Step 5: Execution continues in b.js with a remaining uninitialized. When a is called on line 3, the program errors with: ReferenceError: Cannot access 'a' before initialization. To summarize, the circular dependency results in code being executed with uninitialized values. This could result in various errors, like the ReferenceError above. Why do circular dependencies sometimes not cause errors? JavaScript’s imports are described as “Live Bindings.” 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. I once worked in a code-base that was chock full of circular imports but none of them ever caused any issues. Why? It’s because all the code was defined in functions, none of which would be called until after everything was loaded. To demonstrate, we can update the last scenario to work in a similar way: Click to view a larger version of this image. Steps 1-4 are the same as above but things start to change at step 5: Step 5: a remains uninitialized but instead of being called directly, it is placed in a function definition (no error). Step 6: With b.js completed, execution in a.js continues down to line 3, which defines the export for a. From this point on, any code calling a will get an initialized value, as a result of the live bindings. Step 7: We call a() successfully, which in-turn calls b(). Ultimately, all the code gets called with no errors. 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 a is only retrieved when the variable is actually used. 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. Preventing circular dependencies While JavaScript may not have built-in circular dependency checking, we still have options for preventing these issues. 3rd-party tools like madge and eslint-plugin-import 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. Of course, the best prevention is a well-organized codebase, with a clear hierarchy for shared code. What about Node / Bun / Webpack / etc? 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? In short, yes. In my experiments, I was surprised to find that the error outcomes for browser, server, and bundler were basically the same. For example, with Webpack, the import statements were removed but the combined code still produced the same error: // b.js console.log('b.js:', a); // ReferenceError: Cannot access 'a' before initialization const b = 'B'; // a.js console.log('a.js:', b); const a = 'A'; I should also mention that while Node.js produced the same error when using the import syntax (ESM), it behaved differently when using the require syntax (CommonJS): $ 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 It's nice that the warnings say "circular dependency" explicitly, when using CommonJS. This makes sense when you consider that CommonJS is an entirely different import system that doesn’t conform to the ECMAScript Modules spec. Comparing the two is comparing apples and oranges! Conclusion 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. If you want a closer look at my test results, feel free to check out the repo.
“Civilization advances by extending the number of important operations which we can perform without thinking about them.” – Alfred North Whitehead 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. 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. But not all technology is successful at this. The power of technology is abstraction and a poor abstraction 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. Simon Sarris has a great post describing some of these issues: “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.” Simon Sarris, Careful Technology 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. 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 “the cozier feeling of home.” 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.
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. 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. 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 why it worked, and that was bothering me. 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 real issue, quietly suppressing the error, and hiding the truth in its complexity. 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. 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 thought we fixed it several times but the issue kept coming back. Why? Because we didn’t understand the problem. We kept digging and eventually we found it: one of our dependencies was storing massive amounts of data on the window object and it wasn’t getting cleaned up. We added a cleanup step and the problem was gone for good. Since then, I’ve adopted a mantra: you can’t fix a problem you don’t understand. It doesn’t matter if the “fix” comes from AI, Stack Overflow, or trial-and-error. If I don’t understand the problem, I feel unsettled until I do.
Here are some things I made in 2024: Music Box Fun: Advanced Editing (a new major feature): Adds multiple-note selection for bulk operations on notes (like deletion, copy/paste, nudging and dragging) Also includes a “space editor” for arbitrarily adding/removing space anywhere in the song Includes other niceties like note highligting during playback and pitch highlight on hover Music Box Fun songs I made: Elliott’s Theme (Stardew Valley) in 15-note and 30-note versions Bluey Theme Song Jupiter Theme (The Planets) 11 new projects added to Let’s Get Creative, now available at a new domain name: https://letsgetcreative.today The Firefly Building in Minecraft (if you know, you know) 13 blog posts on bryanbraun.com, including a companion repo to my post on unusual Git IDs. 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. 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. 🚵🏻♀️🚵🏻♂️ I wish you all the very best in 2025.
More in technology
As we pack our bags and prepare for the adult-er version of BlackHat (that apparently doesn’t require us to print out stolen mailspoolz to hand to people at their talks), we want to tell you about a recent adventure - a heist, if you will. No heist story
For your small business to survive, you need customers. Not just to buy once. You need them to come back, tell their friends, and trust you over time. And yet, too many small businesses make it weirdly hard to talk to them. Well, duh, right? I agree, yet I see small businesses fumbling this over and over. All the attention when discussing business is about giant corporations. Whether they’re selling servers or vehicles or every product under the sun, millions of dollars pass through their doors every day. Yet it is folly to apply the methodologies of giant companies to our small businesses. It sounds obvious, but I constantly see small businesses making it hard for customers to get in touch. If a customer does get through the “contact us” gauntlet, that small business often uses needlessly complicated enterprise software to talk with customers. Small businesses don’t get the spotlight, but they are the engine of the economy. To wit, in the United States: 99.9% of businesses are small Nearly half the private workforce is employed by small businesses They generate over 43% of the country’s GDP And beyond the stats, small businesses are who we turn to every day: your corner coffee shop, your local cleaner, your neighborhood software team. And don’t forget that every big business started small. Small businesses are the genesis of innovation. We all need small businesses to succeed. Most small teams aren’t trying to become giant corporations. They want to make a living doing work for a fair return. Many of them work hard in hopes of moving the needle from a fair return to a comfortable life, and maybe even some riches down the road. Yet it’s amazing how often it’s forgotten: you need customers to succeed. Success in small business starts with human conversation. While talking effectively with your customers does not guarantee success, it is certainly a requirement. Here’s what that looks like: a customer has a question and your team responds kindly, clearly, and quickly. Or sometimes your team wants to reach out with a question for a customer. It’s a simple, human interaction that cannot be done effectively by automation or AI. It’s the air your small business is breathing. Starve that air, and everything else suffers. Your product or service is almost secondary to building a healthy relationship with each of your customers. Big business doesn’t operate this way. We shouldn’t expect it to show us how to build real relationships. We’re doing our best here at Good Enough to build healthy, happy customer relationships. Whenever you write to us about any of our products, someone on the team is going to reply to offer help or an explanation or an alternative. Fact is, if you write to us about anything, we’re going to reply to offer help or an explanation or an alternative. As an online business, we’re talking with customers primarily over email. For us, Jelly makes those conversations easy to have—human, not hectic. Actual customer support is remarkable. Actual, healthy human relationships are important. Actual customer conversations are a key to small business success. Choose your actions and tools accordingly. If you liked this post, maybe you’ll like Jelly, our new email collaboration app for small teams!
You can’t throw a rock these days without hitting someone trying to build humanoid robots.
I know there’s been a lot of frustration directed at me specifically. Some of it, I believe, is misplaced—but I also understand where it’s coming from. The passing of Pope Francis has deeply impacted me. While I still disagree with the Church on many issues, he was the Pope who broke the mold in so … Continue reading Reflecting →