The Sadness of Source Maps

There’s a dangerous phase in every ambitious project. It comes right after you’ve slain a few dragons and the path ahead looks clear. Your core abstractions feel sound, your tests are green, and for the first time in a long time, you’re not fighting the code, you’re dancing with it. You get confident. You get comfortable. The hubris sets in. You start to believe your own hype.


We were deep in that phase. After the breakthrough with file uploads, after proving our core philosophy could withstand the messy reality of binary data, we felt invincible. We had stared into the abyss of compromise and refused to blink. We believed, with the earnest conviction of the recently victorious, that we had finished the hard part. The team’s mood was buoyant, electric. We started building out a proper internal dashboard, a complex application to dog-food our own tools, and for a short, blissful period, the development velocity was nothing short of intoxicating. Features that would have taken weeks of wrangling APIs and state managers were materializing in days, sometimes hours. The dream was real. We were living in a fool's paradise.


And then, the ghosts started appearing.


It wasn’t a single, dramatic failure. It was a slow, creeping rot. The death of a thousand tiny cuts. It began not with a crash, but with a flicker of doubt, a feeling that was just… wrong. It happened in the most mundane of places: a simple "Add to Cart" button. The logic was trivial. Click the button, call server(), push an item ID into a user’s session array on the database. Three lines of perfect, declarative code. But when we actually clicked it, the UI betrayed us. There was a pause. A hiccup, a stutter, a barely-perceptible but deeply unsettling delay of about 150 milliseconds between the physical click and the button’s state changing to "Adding..." and disabling itself.


We knew why instantly, and the knowledge felt like a stone in our stomachs. The button's disabled state was derived from the central app.db object, our single source of truth, which lives on the server. For that button to disable itself, our click event had to embark on an epic journey: a packet traveling from a browser in London, across the Atlantic to our server in Virginia, where the in-memory database would be updated. Only then could the reactive update begin its long voyage back to London to finally alter the DOM. Our global "nervous system," so brilliant for synchronizing state between multiple users, was a dogmatic tyrant for the single user interacting with their own screen. The instantaneous feedback loop that makes an interface feel alive and responsive was broken. The UI felt sluggish.


The button felt like a liar.


Our first solution was a hack. A dirty, shameful, embarrassing hack. Before the server() call, we surrendered. We broke the spell and wrote button.disabled = true; button.textContent = 'Adding...';. We manually, imperatively, mutated the DOM. It worked, of course. The button now felt instantaneous. But it was a profound betrayal of everything we were trying to build. We were now writing imperative code to patch over the flaws in our supposedly elegant declarative model. We had immediately created a race condition: what if the server call failed? We would have to add a .catch() block to manually revert the button’s state. We were right back in the hell of manual state synchronization we had fought so hard to escape.


That one ugly hack opened the floodgates. The purity was gone, and once you’ve made one compromise, the next one is easier. To identify which item to add to the cart, we stopped trusting our framework's magical scope-capturing. It was too much of a black box. We reverted to old habits, stuffing state into the DOM itself: <button data-item-id={item.id}>. The clean onClick handler devolved into a mess of event.target.closest('button').dataset.itemId. We were storing application state in markup strings. We were re-implementing state management, badly, on top of a framework that was supposed to have solved it. The "Locality of Behavior" we had so proudly championed was rotting from the inside out, sacrificed at the altar of a responsive button.

at anonymous (eval at <anonymous> (zero/engine/core.ts:431:15))
at Object.execute (zero/engine/core.ts:437:3)
at WebSocket.handleMessage (zero/runtime/server.ts:182:11)...


The logical next step would be to adopt a standard isomorphic architecture: render an initial HTML string on the server, then "hydrate" it on the client to bring the application to life. This is where a new monster would appear. A naive approach to hydration would require us to serialize all the data used in the render—in our case, the entire array of todo objects, with all their fields—and embed it in the initial HTML payload for the client to consume. We would be trading our current data transfer efficiency for ecosystem compatibility, creating precisely the kind of bloated, over-fetching nightmare we despise. It would feel like a colossal step backward. The compiler, however, is what lets us leapfrog over this trap. It's what allows us to build a smart isomorphic system, not a naive one. It's the key that lets us gain access to the entire React ecosystem without sacrificing our soul to clumsy data transfer.


Embracing a build step allows us to move from this simple SSR to a truly intelligent, isomorphic React model.


The plan crystalized, and with it, our despair turned into a new kind of excitement.


The compiler would become our specialist, our optimizer. When it analyzes your App.tsx file, it won't just see code; it will see intent. It will see that while you assign Object.values(app.db.todos) to a variable, you only ever actually *use* two things from that collection: todos.length for a counter and todo.id inside your map loop. todo.text, todo.done, createdAt—none of it is ever touched.


Based on this static analysis, it will automatically generate a hyper-efficient, specialized data subscription just for the App component. On the server, this subscription will watch for exactly two things: a change in the total number of todos, and a change to an existing todo's id.


Then, it will move on to your TodoItem component. It will see that *it* uses todo.text and todo.done. It will also understand that this component is parameterized by a specific id passed down from its parent. The compiler will then generate a *separate, tiny subscription for each instance* of the TodoItem, which only cares about the text and done fields for its specific id.


For you, the developer, absolutely nothing changes. You still write const todos = Object.values(app.db.todos);. It still just works. But the underlying reality is transformed. A change to a single todo’s text will no longer cause the entire list to be re-evaluated. It will send a few bytes of data over the wire only to the single, specific TodoItem component that changed. The compiler will wrap your components in an invisible provider that manages these subscriptions, creating them on mount and tearing them down on unmount, all multiplexed over a single WebSocket. Your code stays simple, but the performance becomes surgical. It is the ultimate fulfillment of our vision: the tool does the hard work so the developer doesn't have to.


This is the compromise. It feels like a defeat, and in many ways, it is. We failed to create a world without a build step that was also a world a developer could happily live in. Our beautiful idea was flawed.


Our new philosophy must be this: the build step must be invisible. It must be zero-configuration, lightning-fast, and completely transparent to the developer. When you run deno run, it will just work. You should never have to think about it. We are compromising on the letter of our law to uphold its spirit. And right now, the most complex thing in our project is trying to make a button feel fast while simultaneously trying to figure out where the hell that undefined is coming from.


We haven't written a single line of the compiler yet. We just know we have to. This is us, in our public notebook, admitting our failure. And now, to save our dream, we have to do the one thing we swore we would never do.