<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://frantic.im/</id>
  <title>frantic.im</title>
  <link rel="self" href="https://frantic.im/feed.xml" />
  <icon>https://frantic.im/favicon.png</icon>
  <subtitle>Occasional posts on technology and stuff</subtitle>
  <updated>2026-05-22T15:38:26.815Z</updated>
  <author>
    <name>Alex Kotliarskyi</name>
  </author>

  
    <entry>
      <id>https://frantic.im/symphony</id>
      <title>Building Symphony</title>
      <updated>2026-05-01T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/symphony" />
      <summary>Behind-the-scenes notes about the agent orchestrator I built at OpenAI</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>We just published a <a href="https://openai.com/index/open-source-codex-orchestration-symphony/">blog post about Symphony</a> – an agent orchestrator I built for my team at OpenAI. Here I wanted to share a few behind-the-scenes bits that didn’t make the cut.</p>
<p>Earlier this year I joined a team that had gone all-in on Codex. And it worked! It worked because we designed the software in a way that maximizes the benefits and keeps technical debt under control.</p>
<p>Now we were constrained on human attention. Even the smartest engineers lose track of parallel Codex sessions after working on half a dozen tasks for hours.</p>
<p>Symphony started as a way to make my teammates happier and break the ceiling of the 5 Codex sessions per person.</p>
<h2>Basic Idea</h2>
<p>In short, Symphony is an orchestrator – it takes a <a href="https://linear.app/">Linear</a> board, and for each open unblocked task it makes sure an agent is running. Think Kubernetes for agents where the task board is the control plane. On top of that it allows us to define success criteria and shift from steering agents to reviewing their “proof of work” packets.</p>
<p>There’s a lot more detail on what it is and how it works in the <a href="https://openai.com/index/open-source-codex-orchestration-symphony/">blog post</a> and the <a href="https://github.com/openai/symphony/">Github repo</a>. Both have a video that explains the idea in 60 seconds (I enjoyed recording that piece very much).</p>
<h2>How I Use Symphony</h2>
<p>In practice I use Symphony for all sorts of things.</p>
<p>First, the bug fixes from our backlog, implementing basic features, all the obvious stuff that could be easily delegated. The tricky part was unrelated to Symphony itself – I had to set up some infrastructure to allow us to record videos from the model’s testing process (think playwright but with a few extra hoops). But once that was in place, I felt like I finally got control over my time and ended the doom of context switching.</p>
<p>Second, I had Symphony orchestrate massive migrations, for example from a homegrown framework to tRPC. I started with a ticket to analyze the codebase and make a plan, then after review Symphony created a tree of tasks. Tasks from phase C depended on phase B which depended on phase A. 20+ tasks organized in a tree that naturally unfolds: as it was done with tasks from phase A, the tasks from phase B got unlocked and it started working on that.</p>
<p>Third, I use Symphony for speculative work, things like “What if we rewrite our backend in Rust?”. In the past I wouldn’t even start this work because it had a cost – I had to spend my time and context switching on an interactive session. Now I can just file a task and see where it ends up. Sometimes it’s throwaway work, but sometimes it yields very useful information.</p>
<p>I often hear this question: “How do you steer Codex?” My answer is: not at the session level! One just has to admit that interactive steering doesn’t work in the long run, just like micromanaging doesn’t work at scale. You have to shift focus to systems, not individual sessions. If Codex often gets something wrong or doesn’t test its work in the way you want, that means some context wasn’t encoded in your codebase.</p>
<p>In practice, if a task is defined too loosely and I see Codex go absolutely sideways, I sometimes use an escape hatch: I have a special ticket state named “Rework”. I modify the ticket description and send it to Rework, my workflow instructs Codex to start from scratch and avoid taking the old approach.</p>
<p>I wish I could tell more about how other teams use Symphony. Many people surprised and delighted me in the cleverness, some having as many as 12 different ticket states to track work across a much larger <a href="https://en.wikipedia.org/wiki/Systems_development_life_cycle">SDLC</a>, others using Symphony for non-programming work.</p>
<p>But Symphony is definitely not magic and doesn’t allows you sidestep good system design. Here’s an example of that.</p>
<p>To stress-test Symphony, I made a simple TODO app without any agent-specific setup. I created a ticket called “Manage This Project” with instructions to check on the board every so often and file feature requests in an infinite loop. These tickets got picked up by Symphony and put to work.</p>
<p>The result was a total chaos. Without good engineering setup, parallel agents constantly broke each other’s work, got stuck resolving conflicts and struggled to get a working dev environment.</p>
<p>(On the bright side Symphony itself worked just fine under the load, easily chewing through millions of tokens per second, which was the main point of the test.)</p>
<p>This highlights that even the best agent orchestration harness doesn’t automagically solve software engineering problems. We have a lot of interesting work to do!</p>
<p>If <a href="https://openai.com/index/harness-engineering/">Harness Engineering</a> is step one, Symphony is step two. But you can’t get to step two without the solid foundations of step one!</p>
<h2>Spec</h2>
<p>Symphony is open sourced as a spec. It’s not really a novel idea, software design docs and waterfall have been around forever, and since the AI boom many people talked about markdown becoming the next programming language.</p>
<p>But I find open-sourcing a spec a really interesting pragmatic point on <a href="https://frantic.im/plotting-ideas/">the spectrum of specificity</a>. It’s definitely more than just the idea, it includes a useful level of detail (e.g. about non-obvious edge cases, or clever design choices that make the system more elegant). But it’s also not as rigid as an off-the-shelf implementation. It invites hackers to make it theirs – implemented in the language and ecosystem they already use with additional features only they care about.</p>
<p>Here are a few of things we’ve added to the spec that weren’t so obvious to us from the beginning:</p>
<ul>
<li>Workspaces are decoupled from Git. The initial versions tried to add enough configuration to support worktrees vs full checkouts, but we realized shell script hooks give more flexibility without raising the complexity.</li>
<li>The split between deterministic configuration and free-form model instructions. I think we landed on a useful and pragmatic split inside <code>WORKFLOW.md</code>, but it took a few iterations to get there.</li>
<li>Giving the model a limited number of turns before we restart the thread. In practice this avoids awkward situations where the existing context gets the model to spin its wheels forever.</li>
<li>Making sure the agent only works on tasks that are not blocked by other tasks, this unlocks more complex workflows while being easy to grasp.</li>
</ul>
<p>Compare that to the core idea: “Orchestrator that guarantees a running agent for every open task”. Useful, but not really enough to turn this into usable software as-is.</p>
<p>Also compare that to the existing <a href="https://github.com/openai/symphony/tree/main/elixir">reference implementation</a>: it’s built in Elixir which might not be your cup of tea.</p>
<p>A note on Elixir: it’s awesome. I always wanted to build something on Erlang’s runtime and this was a perfect fit. Agent threads map really well into BEAM processes, the state is explicit and it’s nice to know that supervision trees will make sure things are chugging along despite bugs and runtime problems. Hot reloading is magical, I could change system behavior without stopping the agents. Also, Codex is really good not only at writing Elixir but also at the interactive REPL – it’s so cool to connect it to your running system and let it inspect the state to figure out problems.</p>
<p>As for building the spec, I started from the reference implementation and tried to extract useful concepts into textual description. I iterated on it multiple times, asking Codex to implement the spec in different programming languages, then evaluating each one and fixing gaps.</p>
<p>The spec is not perfect, and maybe it’s not the ultimate AI software form yet, but I like thinking about the wide spectrum of specificity. See also my post about <a href="https://frantic.im/plotting-ideas/">plotting ideas</a>.</p>
<h2>Shoutouts</h2>
<p>I’m grateful for to <a href="https://x.com/_lopopolo">Ryan</a>, <a href="https://x.com/z">Zach</a> and the team for letting me experiment. I also want to thank <a href="https://www.linkedin.com/in/victorszhu/">Victor Zhu</a> for being the champion of open sourcing and talking publicly about Symphony.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/bored</id>
      <title>Willingness to Get Bored</title>
      <updated>2026-03-09T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/bored" />
      <summary></summary>
      <content type="html"><![CDATA[
        
        
        
        <p><a href="https://blog.vjeux.com/">Vjeux</a> once told me a story that stuck with me.</p>
<p>It was about the good old days of the <a href="https://worldofwarcraft.blizzard.com/">World of Warcraft</a>. Blizzard shipped regular updates to the game, but their change logs were very sparse. The community had to figure out the details on their own, and there were a few websites that tried to provide in-depth reviews of the new stuff.</p>
<p>But one of them had an unparalleled level of details. Every little thing was meticulously documented.</p>
<p>People tried to figure out their secret. Did they disassemble the binaries? Did they have someone on the inside leaking the information?</p>
<p>The answer was less magical. The author simply spent hours and days testing the updates manually. It was a lot of labor. Wasn’t it boring? It was. But they were <em>willing to get bored</em> and do the grunt work.</p>
<p>I see parallels to this story everywhere. Progress often comes from someone willing to do boring work. Even writing this blog post required the willingness to get bored editing and rewriting things multiple times.</p>
<p>This is especially insightful today, in the dawn of AI coding. These tools give us so much speed boost and excitement that we are unwilling to slow down.</p>
<p>Everyone I know went through the same experience. The coding agent fails to one-shot the problem in the way its operator expected. The operator either thinks it’s dumb or that they are missing a clever trick to make it work.</p>
<p>But it’s not dumb. And there’s no trick. One just needs to be willing to do the boring work of explaining the context to the model in a <a href="https://openai.com/index/harness-engineering/">durable way</a>.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/suddenly</id>
      <title>Slow, Then Suddenly</title>
      <updated>2026-02-16T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/suddenly" />
      <summary></summary>
      <content type="html"><![CDATA[
        
        
        
        <p>Many years ago, I was invited to an adventure. My colleague from Facebook was assembling a little skunk works crew for a mission at Apple’s headquarters. We were to build the first prototype of the Facebook app for Apple TV. It was 2014.</p>
<p>An engineer from Apple met us at the Infinite Loop. His task was to show us the new APIs and answer technical questions.</p>
<p>We got to work. But instead of building the actual app, we jumped into building the infrastructure. We ported React to work with then not-yet-announced TVML, used the limited APIs of their JS flavor to talk to our GraphQL servers, set up auth, CI and many other developer experience basics.</p>
<p>After a day of this, no progress was made on the actual app. The Apple guy was neither impressed nor disappointed – it was normal for big companies to bring their engineers to Apple’s HQ for “partnership” collaboration sessions that produced zero value.</p>
<p>But we went home excited for the next day. We knew what leverage we built for ourselves. I kept hacking on the CI/CD pipeline well into the night, reducing the overhead down to a few seconds.</p>
<p>The next day started just like the previous one. After coffees and small talk, our host dived into reading the news, expecting nothing significant.</p>
<p>But the next time he looked at the shared monitor connected to the Apple TV, something very strange was happening. We had the first version of the app on the screen. And then the second. And the third. In less than an hour we iterated on several different concepts, the UI changing almost instantly and showing real production data. We had our friends list, photos and videos show up on the TV, we could rearrange everything.</p>
<p>The Apple guy was blown away. From his perspective we made almost no progress at first but then everything happened all at once.</p>
<p>From my perspective, I was having the time of my life. The system translated our intentions into the real product at an insane speed. Any idea you had for the experience, you could make it and see it on the real device right away.</p>
<p>If you look at what’s happening with AI software development early 2026, it does seem very familiar. A handful of engineers investing in their software factories, which from the outside look unnecessarily complicated and are yet to produce anything of value. But don’t dismiss them yet. Pay attention.</p>
<p>Slow at first, but then suddenly all at once.</p>
<p>P.S. That speed was intoxicating, but in the end it didn’t matter. The Facebook Apple TV app never shipped. The leadership poured all resources into mobile and didn’t have time for the side quests.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/remix-3</id>
      <title>Thoughts on Remix 3</title>
      <updated>2025-10-12T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/remix-3" />
      <summary>The pendulum is about to swing the other direction</summary>
      <content type="html"><![CDATA[
        
        
        
        <p><a href="https://remix.run/">Remix</a> is a web framework by <s>React underdogs</s> authors of the most popular React package. A few days ago at <a href="https://remix.run/jam/2025">Remix Jam 2025</a>, Ryan and Michael shared a sneak peek of Remix v3. There’s no official blog post or documentation yet. Here’s my attempt at explaining what it is about.</p>
<p>Remix v1 was a React framework that managed data loading and server-side rendering. Its biggest achievement was the <a href="https://remix.run/">marketing website</a>. Early Remix was interesting because it was viewed as the first real contender for Next.js’s dominant position.</p>
<p>Remix v2 struggled with its messaging and identity. One of the authors <a href="https://x.com/ryanflorence/status/1791479313939976313">described it</a> as <em>“Remix v2 is a Vite plugin that makes access to React Router v6 features more convenient through the Route Module API”</em>. This is definitely not how you beat Vercel’s marketing machine. The framework had its users, but definitely not the trajectory to make a dent in Next.js’s market share.</p>
<p>Remix v3 is… very different. It represents a broader shift in web development sentiment and could be worth paying attention to. To understand why, let’s look at the state of modern React.</p>
<h2>Modern React</h2>
<p><strong>In 2025, React feels complicated.</strong> React Server Components introduced a more sophisticated way to render components on the server and hydrate them on client. The rules of what is allowed are nuanced: server components can be defined as <code>async</code> now and pass data to client components (but not all kinds of data). Regular server-side functions can now be used on client via magic RPC actions. Developers need to learn <a href="https://react.dev/reference/rsc/use-client"><code>&quot;use client&quot;</code></a> vs <a href="https://react.dev/reference/rsc/use-server"><code>&quot;use server&quot;</code></a>.</p>
<p>The speed, especially on big apps, started to be a common concern. Frameworks like Svelte introduced a way to sidestep virtual DOM diffing and produce the most efficient direct way of updating elements, while still looking reasonably declarative. So the React team opened access to the React compiler, a project that has been brewing up inside Meta for almost 8 years. It works by auto-memoizing your components and hooks. But not all components, because that could introduce subtle bugs. Now it’s about <a href="https://react.dev/reference/react-compiler/directives/use-memo"><code>&quot;use memo&quot;</code></a> and <a href="https://react.dev/reference/react-compiler/directives/use-no-memo"><code>&quot;use no memo&quot;</code></a> (aliased as <code>&quot;use forget&quot;</code> and <code>&quot;use no forget&quot;</code> which doesn’t help with the confusion).</p>
<p>The core API surface of React keeps growing. React has added async rendering APIs (<a href="https://react.dev/reference/react/Suspense"><code>Suspense</code></a>, <a href="https://react.dev/reference/react/startTransition"><code>startTransition</code></a>, <a href="https://react.dev/reference/react/useDeferredValue"><code>useDeferredValue</code></a>, <a href="https://react.dev/reference/react/useActionState"><code>useActionState</code></a>) to make web apps more responsive – now user interactions can be prioritized against less urgent but expensive component tree updates, but require you to reason about “active” and “pending” states. The other new APIs are also seen as complex and niche. Just look at the documentation for <a href="https://react.dev/reference/react/useEffectEvent"><code>useEffectEvent</code></a> (but try to guess what it’s doing before you click).</p>
<p><strong>Nobody forces developers to use these features.</strong> In fact even the oldest <code>createReactClass</code> with mixins work just fine with the official <code>create-react-class</code> npm package. You can still use that style of React today, you don’t have to adopt any of the new modern features.</p>
<p>Except, <a href="https://react.dev/learn/creating-a-react-app">you kind of have to</a> – these features are shoved into developers’ throats via <a href="https://nextjs.org/">Next.js</a>. React itself gave up attempts at being a framework. It was just too much work to keep up with fast paced JS tooling, and Facebook had their own setup not worth opensourcing.</p>
<p>Next.js itself went through some painful transitions (<a href="https://nextjs.org/docs/pages">pages router</a> vs <a href="https://nextjs.org/docs/app/getting-started">app router</a>) and left developers who live on the bleeding edge badly hurt. On top of the API churn, Next.js has a whole another layer of complexity caused by Vercel. Vercel deploys fullstack Next.js apps in its own special way – some code runs in the browser, some in AWS Lambda (with 3x markup), and some in its proprietary worker runtime (“middleware” must be one of the biggest lies that caused developer-decades of confusion and frustration).</p>
<p>All this stuff is just… too much. In fact, even explaining the problems these features and frameworks are trying to fix took Dan several <a href="https://overreacted.io/react-for-two-computers/">conferences</a> and <a href="https://overreacted.io/the-two-reacts/">blog posts</a> to describe. People who get it, get it. RSC, Suspense, React Compiler are really powerful and solve real problems some companies have.</p>
<p>On top of that, LLMs suck at React. When asked to build an app, they all reach for React, but the resulting components are ugly soups of <code>useEffects</code> and random hacks to work around subtle bugs.</p>
<h2>Remix v3</h2>
<p>So in this state of complexity and frustration, Remix v3 is born. You can watch the <a href="https://remix.run/jam/2025">Remix Jam</a> recording (look for timestamps in the comments) + scroll through both authors’ X accounts to get a sense of it.</p>
<p>On the frontend Remix still uses JSX, but there’s no React runtime. It doesn’t track state the way React does, but instead gives developers a <code>this.update()</code> function to call to tell Remix that something has changed. Instead of explicit state you can use anything, most commonly closure-captured variables:</p>
<pre><code class="language-typescript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Counter</span>(<span class="hljs-params"><span class="hljs-variable language_">this</span>: Remix.Handle</span>) {
  <span class="hljs-comment">// State is just a closure</span>
  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{count}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;p-2 text-green-500&quot;</span>
        <span class="hljs-attr">on</span>=<span class="hljs-string">{dom.click((event,</span> <span class="hljs-attr">signal</span>) =&gt;</span> {
          count++;
          this.update();
        })}
      &gt;
        Inc
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>This makes code more imperative, mechanically simpler. Do this, then do that. Transitions, instead of React’s promise of <code>view = f(state)</code>.</p>
<p>Events are first class and use web’s built-in events mechanism. Instead of <code>onClick</code> there’s a universal <code>&quot;on&quot;</code> prop and a library of standard <code>dom</code> events + developers can define their own <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent"><code>CustomEvents</code></a>.</p>
<p>To control async work, Remix relies on <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal">signals</a>. E.g. to cancel a <code>fetch</code> when component unmounts the developers can do this:</p>
<pre><code>function Cities(this: Remix.Handle) {
  let list = [], isLoading = true;
  fetch(&quot;https://api.remix.run/cities.json&quot;, {
    signal: this.signal // &lt;-
  })
    .then(response =&gt; response.json())
    .then(data =&gt; {
      list = data;
      isLoading = false;
      this.update();
    });

  return () =&gt; …
}
</code></pre>
<p>Remix will ship with a component library. React has a wide ecosystem of components that won’t work in Remix, so to stay competitive the team is working on high quality built-in components. Things like menus and forms with attention to detail and accessibility support. It also introduces subtle but nice quality-of-life improvements over React: built-in <code>css</code> prop, <code>class</code> instead of <code>className</code>.</p>
<p>On the backend, Remix double-downs on Web Platform. The handlers take web <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request"><code>Request</code></a> and return <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response"><code>Response</code></a>. It also brings things like <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData"><code>FormData</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/File"><code>File</code></a> and others to the server. This makes the server runtime an implementation detail. Node, Deno, Bun – all have either native support or adapters for these web APIs, so Remix can run anywhere.</p>
<p>In v3 the framework gave up on file-based routing. Just too much trouble to capture the range of things they want to support in just the file names. Instead it introduces a TypeScript-based way of defining the routes. TypeScript guarantees all routes are implemented and URL parameters are passed to handlers and links are never broken.</p>
<pre><code class="language-typescript"><span class="hljs-keyword">let</span> routes = <span class="hljs-title function_">route</span>({
  <span class="hljs-attr">home</span>: <span class="hljs-string">&quot;/&quot;</span>,
  <span class="hljs-attr">about</span>: <span class="hljs-string">&quot;/about&quot;</span>,
  <span class="hljs-attr">books</span>: {
    <span class="hljs-attr">index</span>: { <span class="hljs-attr">method</span>: <span class="hljs-string">&quot;GET&quot;</span>, <span class="hljs-attr">pattern</span>: <span class="hljs-string">&quot;/&quot;</span> },
    <span class="hljs-attr">create</span>: { <span class="hljs-attr">method</span>: <span class="hljs-string">&quot;POST&quot;</span>, <span class="hljs-attr">pattern</span>: <span class="hljs-string">&quot;/&quot;</span> },
    <span class="hljs-attr">show</span>: <span class="hljs-string">&quot;/books/:slug&quot;</span>,
  },
});

<span class="hljs-comment">// Implementation on server</span>
router.<span class="hljs-title function_">map</span>(routes.<span class="hljs-property">books</span>, booksHandlers)

<span class="hljs-comment">// Reference on clients</span>
&lt;a href={routes.<span class="hljs-property">home</span>.<span class="hljs-title function_">href</span>()}&gt;…&lt;/a&gt;;
</code></pre>
<p>The server also comes with built-in sane things like logging and file storage.</p>
<p><a href="https://www.youtube.com/watch?v=zqhE-CepH2g">The gap</a> is what happens between the client and the server. Remix v3’s approach feels a lot less magical than RSC. There’s no custom JSON hydration streams. Instead Remix reinvents iFrames by creating async loading boundaries and uses HTML as wire format, <a href="https://htmx.org/">HTMX</a>-style. There are also hints of using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">Web Components</a> to bring together HTML and JS.</p>
<h2>Summary</h2>
<p>Remix keeps leaning into the Web Platform and TypeScript, takes control of a wider portion of the full stack, ditches nuanced parts of React for a simple <code>this.update()</code>, which makes the code less magical and easier to understand for humans and LLMs. Remix is the <a href="https://grugbrain.dev/">Grug Brain</a> version of what Next.js should have been.</p>
<p>So what happens next?</p>
<p>You’ll hear a lot of noise and hot takes (including this post). YouTube influencers are already recording videos and generating surprised faces for video thumbnails to try to take advantage of the next hype wave. Grumpy Hacker News will complain about yet another JS framework.</p>
<p>It will seem like you have to learn Remix now and migrate all your projects to it. You don’t. React is still fine (nobody got fired for choosing React) and you can keep using the good parts (seems to be a <a href="https://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742">common JS thing</a>).</p>
<p>Remix v3 is a signal of stronger <a href="https://cassidoo.co/post/annoyed-at-react/">frustrations</a> in React community and desire for a change. The functional ↔ imperative pendulum is starting to swing the other direction.</p>
<p><img src="/figma/og_remix.png" alt="" /></p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/music-player</id>
      <title>A Side Project Story: Music Player</title>
      <updated>2025-10-11T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/music-player" />
      <summary>One of my most successful side projects had exactly 2 daily active users.</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>One of my most successful side projects had exactly 2 daily active users.</p>
<p>When I was growing up I loved listening to music, especially my parents’ cassette tapes and CDs. Back then music had a physical form. You had to take a magical artifact and carefully put it into a special machine that would produce sounds.</p>
<p>My kids growing up experience none of that. From their perspective, we do something with our phones and music just appears from a speaker. The cause and effect far apart.</p>
<p>I wanted to change that. I wanted to give them agency over what’s playing, and create an association between a physical medium, label, picture and song.</p>
<p>So here are the principles I had in mind:</p>
<ul>
<li>One physical object == one song</li>
<li>Adding new songs should be cheap and easy</li>
</ul>
<p>I did a brief research online and couldn’t find anything that fits the requirements. And I’ll be honest – I was delighted, it meant I can actually craft something novel and useful for my family and not just buy off-the-shelf solution.</p>
<p>A few days later, I had the “Music Player” (this was way before I started understanding that a good name is important). Each record was an RFID card with an image and title printed on it. You had to take this simple artifact, tap it at a special device and the music would play.</p>
<p><img src="/figma/music_player_components.png" alt="Components of the project: RFID cards, Raspberry Pi, Spotify and Sonos" /></p>
<p>It was powered by an old Raspberry Pi, simple RFID card reader and my existing Sonos speaker. Spotify served as the music library, and adding new songs there was very easy. Here’s how it worked.</p>
<p>The playback part was pretty <a href="https://github.com/frantic/miniplayer/blob/master/scripts/daemon.js">simple</a>: read RFID tag, find a song, send it to play on Sonos (this was the simplest setup because we already had an old Sonos speaker that we used for playing music).</p>
<p>But what made this project successful was how easy it was to add new songs. When we found a song we liked we’d just add it to a playlist on Spotify.</p>
<p>Which brings me to the secret sauce – the admin panel (not an overkill, I promise). It was a React app that used Spotify API to pull a list of songs from a hardcoded playlist ID. It compared the list with the existing database (stored in Firebase) of RFID ID &lt;&gt; song ID mapping, and if the song was missing from the DB, it would pull artwork, name and artist information from the song metadata. Then it rendered the grid of new songs that had a printer-friendly layout that I printed on a sticker paper, cut into individual labels and stuck them to new RFID cards. Then I used a separate RFID card reader plugged into my computer to quickly associate each card with corresponding song.</p>
<p>This process took me about 10 minutes and my kids would help with it too! After a year we had more than 200 RFID-records.</p>
<p>My kids played music on this Music Player every day for years. The youngest user was less than one year old and barely walked, but took no time to learn that tapping with a card plays the song that’s pictured on the card.</p>
<p>Many friends (with families) that visited our house asked for a system like that. I gave them the instructions but nobody ended up building it. I can’t blame them – this project can feel like too much work to fit into a busy parenting life. If you want to try, the source code (with the shopping list of materials) is here <a href="https://github.com/frantic/miniplayer">https://github.com/frantic/miniplayer</a>.</p>
<p>I loved building it, and my 2 favorite customers loved using it.</p>
<p>P.S. As I was polishing this post I saw a <a href="https://fulghum.io/album-cards">similar project by Jordan Fulghum</a>, he used NFC tags &amp; Plex instead of RFIDs &amp; Spotify. Check it out!</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/get-your-shit-together-day</id>
      <title>The Get Your Shit Together Day</title>
      <updated>2025-09-18T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/get-your-shit-together-day" />
      <summary>Everyone needs a Get Your Shit Together day every once in a while...</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>The Get Your Shit Together day is a day off you take from work, family and other obligations.</p>
<p>It’s a day you dedicate fully to things you’ve been always postponing. Fix squeaky door hinges, repot your plant, publish that blog post, start a side project. Really anything on your todo list that has been there for years.</p>
<p>On the Get Your Shit Together day you are not allowed to work on anything typically considered urgent or important. There’s a whole life-worth of days for that.</p>
<p>The Get Your Shit Together day feels good because you finally take action on things that have been eating away at you for so long. Action creates motivation. Motivation makes you want to do more things.</p>
<p>If you read this far and this idea resonates with you, pick a random day next month and block your calendar. I promise it’ll be worth it.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/who-cares</id>
      <title>Who Cares</title>
      <updated>2025-03-30T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/who-cares" />
      <summary></summary>
      <content type="html"><![CDATA[
        
        
        
        <p>In 2017 I gave a <a href="https://www.youtube.com/watch?v=fjS5ssBn3fA">talk</a> at Chain React about building great user experiences with React Native. At the time the tech was new and the quality of the mobile apps built with React Native was poor. The gist of my talk was that it’s possible to build great apps with React Native, one just needs to care enough.</p>
<p>My message didn’t resonate as well as I’d hoped. It’s hard to make people care about quality when the measuring stick of the technology is “look how fast I made this cross-platform app”. In contrast, inside the early iOS dev community, caring for quality was something that everyone shared.</p>
<p>The modern generation of users grew up without knowing what it feels like to use an offline, low-latency, well-crafted apps and websites. There’s little incentive now to spend any resources on improving quality, while your competition spends it on more features and ads.</p>
<p>I love my AI tools. They cured me from <a href="https://frantic.im/side-projects-are-hard">the blank page curse</a>, they help me learn new tech much faster, and produce boring boilerplate code.</p>
<p>But they also make it easier than ever to not care about quality…</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/hn-stack</id>
      <title>The Hacker News Stack</title>
      <updated>2025-01-28T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/hn-stack" />
      <summary>If you want to build a web app The Hacker News Way, here's the stack you should use.</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>If you want to build a web app The Hacker News Way, here’s the stack you should use:</p>
<ul>
<li><strong>Rust</strong> – the only language that gives you the privilege of calling your app <em>“blazing fast”</em>. According to the undocumented Hacker News rules, any post mentioning Rust in the title starts with 10 points.</li>
<li><strong>Postgres</strong> – the most beloved database that can do absolutely everything: manage data, handle row-level authorization, run job queues, replace Redis, vacuum your apartment (or its own tables… who cares?). No ORMs allowed.</li>
<li><strong>HTMX</strong> – because JavaScript is obviously the worst part about web development. Bonus points: you get to call yourself the <a href="https://htmx.org/essays/lore/#htmx-ceo">CEO of HTMX</a>, very helpful in this turbulent job market.</li>
<li><strong>Hetzner</strong> – for just $4.59/mo you can get a server that can easily handle HN’s famous <a href="https://news.ycombinator.com/item?id=20147951">hug-of-death</a>.</li>
</ul>
<p>Make sure you build your app while WFH, commuting to the office was invented by evil corporations.</p>
<p>…</p>
<p>If this post made your smile, maybe you should cut down on reading Hacker News. I know I should.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/best-tool</id>
      <title>No Best Tool for the Job</title>
      <updated>2024-06-19T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/best-tool" />
      <summary>How do you choose the best tool for the job?</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>How do you choose the best tool for the job?</p>
<p>Suppose you want to plant a tree in your backyard and need to dig a hole. You go to the nearby hardware supply store and see these options:</p>
<p><img src="/figma/graden_tools_options.png" alt="" /></p>
<p>You don’t have to be a PhD in Gardening to know which tool is best for the job. It’s self-evident from the size of the tool and its cost.</p>
<p>The next day you decide to build a web app. You go to a catalog of website-building tools and you see this:</p>
<p><img src="/figma/web_app_tools_options.png" alt="" /></p>
<p>Let’s imagine for a moment that you are relatively new to programming. How would you pick the best tool for the job here?</p>
<h2>All these programming tools look the same</h2>
<p>Unlike tools for digging holes, all these websites look the same. They all promise the same thing — build your web app. It’s not clear why you’d pick one tool over the other. So you dig a little deeper…</p>
<h2>Infinite fractal of categorization</h2>
<p>…and discover an almost infinite amount of specialization. With hardware tools, the categories are more defined: here’s a shovel, and here’s an excavator. There’s nothing in between. With programming languages and frameworks, for any two tools, there seems to be another tool in between.</p>
<p>The job of evaluating the tools becomes harder than the job of actually using the tool.</p>
<p>You could just pick any tool and get started, but…</p>
<h2>Too easy to choose the wrong tool</h2>
<p>With hardware tools, the price and the size of the tool give you a strong hint of what it is best for. Sure, using an excavator to dig a hole for a tree sounds like a ton of fun, but buying or renting one is way too expensive. Plus, you need to learn how to drive one, get a license, and it definitely doesn’t fit into your backyard.</p>
<p>With software tools, it costs almost nothing to choose the wrong tool for the job. They all optimize for getting started quickly. Building a static website with GraphQL — easy. Use Kafka for a one-person backoffice app — no problem. You can spin up both in under 5 minutes, for free.</p>
<p>So how are you supposed to make a good choice for your web app? Well, you could search for some advice on Reddit, X, or HackerNews…</p>
<h2>Advice overload</h2>
<p>…and get overwhelmed with the amount of opposite opinions. “React is the best” / “React is the worst”, you should always render things on the server, you should make a single page app, use static types, use dynamic types, functional programming is the way, functional programming is too slow. The list of contradictions goes on and on.</p>
<p>The people shouting the advice sound extremely confident. If you dig deeper, some of them are actually employed by the tech stack they are preaching, but even outside of obvious economic incentives, it’s just too much noise to sort through.</p>
<p>Meta advice is just as polarized. “Pick a tool you already know” vs “learn new things”, “choose a popular tech stack with a large community” vs “choose niche tech stack with a great community”, “focus on clean code” vs “focus on business outcomes”.</p>
<p>—</p>
<p>So my mind gets <a href="https://frantic.im/side-projects-are-hard/">stuck</a> on this one. How do you pick the best tool for the job?</p>
<p>The only way out of this pickle situation I found so far is to turn around and face the question itself. What does “the best” even mean?</p>
<p>Back to the tree planting, the choice is simple, so it’s obvious which tool is the best. In programming, we no longer have <a href="https://youtu.be/0Ttw9ks05G4?si=XR4z-Jwj825p7fkI&amp;t=222">that luxury</a>, thus maybe the concept of “the best” doesn’t work anymore. The options are just too close, too nuanced, and the consequences are too fuzzy. Even if you are an expert in these tools, there are still enough unknowns to make the choice non-trivial.</p>
<p>When the options can’t be plotted on a single axis with clear “good” and “bad” fits, there’s no “best”. There’s only “good enough”. And if we give up chasing “the best tool for the job”, we might just free up enough mental space to use “the good enough tool” to actually plant that tree.</p>

      ]]></content>
    </entry>
  
    <entry>
      <id>https://frantic.im/opening-mail</id>
      <title>Opening Mail</title>
      <updated>2024-01-06T12:00:00+00:00</updated>

      <link rel="alternate" href="https://frantic.im/opening-mail" />
      <summary>First make the change easy, then make the easy change.</summary>
      <content type="html"><![CDATA[
        
        
        
        <p>I never liked opening envelopes; they’re tricky and ripping them open is annoying. My letters would get stuck or tear with the envelope.</p>
<p>The mail just used to stack up, and I’d miss important stuff because of it.</p>
<p>But then I found this cool little gadget from Japan.</p>
<a style="border: none" href="https://www.amazon.com/dp/B001GR4DQ8" target="_blank">
  <img src="https://frantic.im/assets/ceramic-letter-opener.jpg" width="256">
</a>
<p>It’s well-made, affordable, and feels good to use. Plus, it’s safe.</p>
<p>The best part? It actually made me enjoy opening my mail.</p>
<p>After this experience, I started thinking differently about unpleasant tasks. Is there a tool or a service that add delight to mundane things?</p>
<p>I also started noticing when people do this subconsciously. For example, most software engineers I know hate blogging, but they like building their own blog engine to make blogging more pleasant (I’m very guilty of this too).</p>
<p>Kent Beck <a href="https://twitter.com/KentBeck/status/250733358307500032">nailed it</a>: “for each desired change, make the change easy (warning: this may be hard), then make the easy change”.</p>

      ]]></content>
    </entry>
  
</feed>