Nobody drew the shape the upload code is taking. No one sat down at the start of EkoLite, our small real time backend, and decided that a file would travel over an XMLHttpRequest behind a nulled transport, that a refused upload would come back in the same error envelope as a failed method call, that the client caller and the socket caller would turn out to be mirror images of one another. We did not draw any of it. It arrived. This is a post about how it arrived, and about the two habits that make arriving feel like progress rather than luck: writing the test first, and integrating all the time.
Watching a shape accrete
The clearest example is the method round trip, because you can watch it build itself a slice at a time. Calling a function on the server from the browser took three small steps, and not one of them drew the finished thing.
The first step was a registry. A little map that holds named functions and runs them, and fails in a predictable way when the name is unknown. No network, no sockets, nothing over the wire. Just define, call, and a clean error for a name that was never registered. We wrote the failing test, made it pass, tidied it, and stopped.
The second step put that registry on the wire. A message of the shape { type: 'method' } arrives on the socket, gets routed to the registry, runs, and the result goes back down the same connection. When the method is missing or throws, an error goes back instead. Still small. Still one slice.
The third step was the client end. A call(name, args) that mints a request id, sends the message, and hands back a promise that the matching reply resolves or rejects. By the time this slice landed, the round trip was whole, and the interesting thing is that it had assembled itself out of three independent pieces that were each only trying to pass their own test.
Where does the shape come from, if not from the drawing? It comes from pressure. You feel the design before you can name it, and the practices are what turn the feeling into a decision.
The forces that pulled it
Testability pulled the first shape. We do not mock here. A wrapper that needs to be driven in a test gets an off switch, a nulled version of itself that behaves like the real thing without reaching the world, and the way you keep that honest is to inject the thing that touches the outside rather than construct it inside your own code. The socket wrapper does exactly that, and when the uploader comes it will copy the move, an injected transport with a real implementation and a null one, because the moment it news up an XMLHttpRequest of its own it stops being testable and we are back to fighting the browser in a unit test.
A need that had not arrived yet pulled the second. The uploader will run over XMLHttpRequest and not fetch, even though the happy path would be shorter with fetch, because a progress bar is coming in the next slice and fetch cannot report upload progress. Choosing the harder transport now is not gold plating. It is putting the seam where the future need already is, so the next story adds a bar instead of rewriting the request. Design, a lot of the time, is just deciding where the seam goes.
Consistency pulled the third. A failing method already comes back as { code, message }. So when the upload route had to start refusing files, the question answered itself: refuse in the same envelope, and let the client reject with the same shape its method caller already rejects with. One error language across the socket and the HTTP route, not two. We are still finishing that convergence, the route has a couple of older replies in the old shape, and we have left a test sitting on that gap so it cannot be quietly forgotten. The shape is honest about being unfinished, and the next post is where it closes.
Green is permission
None of this is tidy, and the practices are not there to make it look tidy. They are there to make change cheap.
At one point a method call could hang forever. If the connection dropped while a call was in flight, the promise never settled, because the teardown remembered to reject the open subscriptions and walked straight past the pending calls. It reached the main branch despite a review. That is the kind of sentence you are tempted to leave out of a blog post, and leaving it out would be the dishonest move, because the whole point is what happened next. One small test, a call started and the socket dropped, expecting a rejection rather than a wait. It went red against the live code and green the moment the teardown settled the pending calls too. Minutes, not a release.
That is what continuous integration buys you, and it is not really about a server running your tests. It is that every piece is integrated all the time, so a single failing test can stand on the whole system at once and say, here, this. When the suite is green you have permission to change the shape, because the thing that would tell you that you broke the round trip is already watching. Take that away and emergent design becomes a euphemism for hoping. Keep it and the design can keep moving, because every move is caught.
Stand where we are standing
You do not have to take our word for any of this, which is the part that matters most. You also do not need a socket to watch the round trip's shape, because the socket is only the registry with a wire in front of it. In a Node session or a test, build a null app and define a method on it, then call it the way the wire would:
import { App } from 'ekolite';
const app = App.createNull();
app.methods.define('greet', (name) => `hello ${String(name)}`);
await app.methods.call('greet', ['world']); // 'hello world'
That is the first two slices with the network taken out: a registry that holds the function, and a call that finds it and hands the result back. Now break it on purpose. Ask for a name nobody registered and watch the predictable failure the very first slice built, a clean rejection rather than a hang or a crash:
await app.methods.call('nope', []); // rejects: method not found
The predictable failure from the first slice, still there at the edge, whether the call came in over a socket or in process.
Design here is a verb. Extreme Programming gives you the small steps, continuous integration gives you the nerve to take them, and the shape shows up. If you ever do draw it, the drawing is the map you make after the walk, not the one you set out with.


