Essay

The parity is in the seams.

Extreme close-up of a hand-cut dovetail joint where two pieces of pale wood interlock, raking light catching the exact seam line where they fit together, the rest falling into shadow.

I have been building a terminal text editor. Not because the world needs another one, but because it is the longest-running thing I work on, and a long bet is where I learn what a thing actually costs. The target is Zed: a fast, modern editor with a language server, a diff view, a completion popup, all the structure a real editor has. Most of that structure already exists in mine. The tabs, the file tree, the find and replace, the multibuffer, the LSP client. The marquee features are mostly built.

So I expected the remaining work to be more features. It is not. Three fixes this week, none of them a feature, and each one taught me the same lesson from a different angle: the distance between an editor that demos and an editor that holds is not in the features. It is in the seams. A seam is the place where a real tool hands you data shaped a little differently than the happy path you wrote assumed. The feature works on the example. The seam is where the example ends.

The order the server sends is not the order it means

A completion popup shows a list. You type, the language server returns candidates, you draw them. Easy. Mine drew them in the order they arrived on the wire, which felt obviously correct, because what other order is there.

There is another order. Every LSP completion item carries an opaque field called sortText, and the protocol says the client must sort the menu by it, falling back to the visible label only when the server omits it. The server uses that field to push the candidate it thinks you actually want to the top, ahead of thirty alphabetically-earlier ones you will never pick. gopls leans on this hard. My popup ignored it and showed whatever order the wire happened to deliver, so the first row, the one a fast typist accepts on reflex, was arbitrary.

The fix is four lines: stably sort a copy of the items by sortText with a label tiebreak before drawing them. The seam was not in the drawing. It was in the quiet assumption that a list arrives in the order it wants to be shown. A real server has an opinion about order and encodes it in a field you have to go read.

The message is a paragraph wearing the shape of a line

The diagnostics panel renders a list of problems, one row each: a location, a severity, a message. I built it to put the message on the row, which is what it is. A message is a string. A string is a line.

Except a real diagnostic message is not a line. rustc appends note: and help: clauses, each on its own line. gopls wraps multi-part errors. The string the server hands you routinely carries newlines inside it. Rendered into a single row as-is, that embedded newline splits one logical entry across two terminal lines, and everything downstream that assumed one row per problem comes apart: the bordered card overflows, the scroll window miscounts, the cursor lands on a half-row. One ordinary Rust error, and the panel breaks.

The fix is one helper that collapses every run of whitespace, newlines included, into a single space before the row is styled. The full multi-line text stays on the entry, so the jump target keeps everything. The display reads as one continuous line, the way Zed and VS Code show it. The seam was the assumption that a message is one line. It is a paragraph that happens to be short most of the time.

The diff's data model is not the user's mental model

The multibuffer stitches the changed slices of many files into one scrollable surface for review. I wanted a motion every reviewer expects: jump to the next change. From here to the next inserted line, skip the context in between. It sounds like a one-liner.

It is a one-liner only once you understand what a change is in a unified diff. git does not have a "modified line." It models a modification as a deleted line plus an added line, and the deleted side does not exist in the new file you are scrolling, so it never appears as a row at all. Which means "the next change" is exactly "the next added line," and a run of five adjacent added lines is one edit, not five. If the motion stepped onto each added line in turn, a reviewer pressing the key through a ten-line insertion would press it ten times to clear one change.

So the motion clears the whole contiguous added run before it lands, and steps between distinct edits instead of down the inside of one. That is the behavior a reviewer means by "next change," and it falls directly out of taking the diff's own data model seriously rather than the mental model of changes as highlighted lines. The seam was the gap between how I think about a diff and how git represents one.

What the seams have in common

Three fixes, three seams, one shape. In each case the feature was already there and worked on the example I built it against. The completion popup drew a list. The diagnostics panel showed a message. The multibuffer scrolled a diff. What was missing was fidelity to the actual shape of the data a real tool produces: an order encoded in a side field, a message with newlines in it, a diff that has no concept of a modified line. None of these is hard once you see it. All of them are invisible until a real language server or a real repository hands you the case the happy path did not expect.

This is the part of the work that does not show up in a feature list, and it is most of the distance to a real editor. The features announce themselves; you know you are missing find-and-replace. The seams do not. You find them one at a time, usually when the thing you already shipped behaves slightly wrong on real input, and each one is a small humbling reminder that "it works" meant "it works on what I tested." The repair is always the same move. Find the seam. Write the test that would have caught it. Move on to the next one.

I used to think parity with a tool like Zed was a matter of building the big pieces. The big pieces turn out to be the easy half. The editor becomes real in the seams, the same way a joint becomes strong not in the boards but in the fit between them.