Green Carets
Test results that land in the editor — green carets on passing lines, every failure a click from the assertion that broke it — remove the manual rejoin between a failing test and the code that caused it. On a legacy JavaScript codebase, removing that rejoin changed how this code gets edited at all.
Part 2 of 3.
The first time I ran that suite from the IDE, the file went green one line at a time. Not a count in a footer, not a summary in a side pane — the lines themselves, in the file I had been staring at all afternoon. Spec after spec lit its own line, and the one I had been wrestling with lit last, green like the rest.
Small thing, until you notice what it implies.
A test run tells you pass or fail. The editor tells you which line — in the file, while you’re still looking at it. Same information, different distance, and distance is the subject of this series. The codebase that taught me this ran on an older Node release: an Express backend with its specs written in Mocha and Chai, and a frontend framework sitting in front of it with its own suite run through KarmaJS. Old tests, old stack, two runners. The suite was not the problem. The loop around the suite was.
The Old Loop
Credit where it’s due, and I want to be precise about this because it matters for the argument: KarmaJS was never the villain. It could watch the files and re-run the suite on every save — and I kept that switched off, because I wanted control over when each runner fired, Mocha and Karma alike. Every frontend run was a deliberate trip, the same as every backend run: Karma kept its Chrome window open, and I fired every run myself. I reached for npm run serve less than you’d think — day to day, running the suite was how I developed against the frontend; the server came out at the end, for the exploratory pass once the specs were already green. One browser, no manual choreography in between. That part of the memory is genuinely good, and I will defend it.
The loop’s problem was where its answer came back. Every run — backend specs through Mocha, frontend specs through Karma — ended in the terminal the same way: a wall of text, dots and checkmarks for the specs that passed, then red entries for the ones that didn’t, each entry a spec name, an error, a stack. Both runners reported; neither pointed. A red entry said a spec had failed and where it asserted; it did not say which of my changes had done it, or whether the failing line was even in the file I was working in. That last part — the rejoin of the failure to the code — was mine to do, by hand, in my head, on every run, for every failure, on either side of the stack.
And a loop with a fixed cost per trip teaches you to pack each trip. Why run after one change when the next three are already in your head? So you batch the edits — and batching does what batching always does. When the run comes back red, it cannot tell you which of your changes did it. Now you are not just rejoining names to lines; you are interrogating your own last hour of edits. Nothing about the old loop rewarded a small edit. It charged the same toll either way, so the rational move was always the big one — on code you don’t own, where the big move is exactly the one you should never make.
The Editor Does the Join
That run came from the IDE — the JetBrains license from part one, finally pointed at the project it was bought for — and it changed nothing about the tests themselves. Same suites, same runners, same results. What changed was where the result went.
The gutter lit up instead of the terminal. Passing specs marked their own lines; a failing one went red on the same line. The reason sat in the run view next door — the failing assertion, its message, the stack — and it took no trip out of the file to read it. The carets weren’t only a readout, either: click one and it runs just that spec, or click the one beside a describe block and it runs just that suite — no detour through a full-file or full-project run to check the one line you actually care about. A failing it() stopped being a name in a log and became a place. The tool did the join: output and code arrived connected, and the translation I used to do in my head simply stopped being my job.
The IDE also did not care which runner was doing the work. Backend specs through Mocha, frontend specs through Karma showed up in the same gutter, with the same green caret and click-to-run-this-one behavior. The tool normalized the two runners into one interface. The suite did not modernize that afternoon — same old tests, same old stack — but the feedback did. On old code you don’t need to modernize the tests to get modern feedback. The tool adapts to the legacy, not the other way around.
Yes… I know what you’re thinking: the terminal already printed the failing spec’s file and line, and terminal emulators have been making those clickable for years. Same information. True — at the moment it prints. But the question was never what the information contained. It was where the information lived. The terminal held it in a surface I had to switch to, buried in a run’s worth of scroll, and it expired on the next run: each trip rebuilt the wall from scratch and pushed the failure I was chasing up and out. The editor held it against the code and left it there until the spec actually passed. Green marks stay green while I work; red marks stay red until they are fixed. The terminal had memory too, if I kept it: scrollback, held by not printing everything, reached by scrolling. On a suite this loud, that scrolling was rarely optional. What it never had was the failure attached to the code that caused it.
When I migrated to WebStorm in 2020, the decision wasn’t ideological. I used whatever got the job done with the least friction between me and a green spec — and out of the box, WebStorm already knew both runners. Point the project at it and the carets are simply there: no afternoon spent wiring Mocha and Karma into an editor, nothing extra to configure or keep working. For a legacy repo running two different runners, that isn’t a convenience. It’s a toll you never pay before the loop even starts.
You Stop Batching Edits
Efficiency is the boring version of this story — faster feedback, more runs per hour — and it is real but shallow. Faster at what you were already doing. The interesting version is what the loop stops charging.
Batching was never a discipline problem. It was the toll, and it was rational given the loop: same wait whether you changed one line or five, so change five. No amount of “just run more often” fixes a toll — that advice asks you to out-spend a rational calculation with willpower. The IDE removed the toll instead of asking me to be braver. When a check costs nothing and answers where you stand, the rational unit of work collapses on its own: change a line, run, read the one mark that matters, change the next line. I did not decide to edit smaller. The loop stopped paying me to edit big, and the habit dissolved without a decision anywhere in sight.
Somewhere in there the discipline flipped into its own bad habit. I started mashing Ctrl+F5 before a change had even finished saving, rerunning the suite the way you hit a crosswalk button that’s already lit. The old loop punished you for checking too often. The new one just… lets you. Turns out that’s its own kind of danger.
Yes… I know, don’t run the suite through the IDE at all. Keep a terminal open beside the editor and glance at it between edits. Split the pane, watch the dots. It preserves the old loop and adds nothing to it, which is exactly the appeal — and here is where it breaks. It breaks on the property that defines code you don’t own: the spec that fails is almost never in the file you are looking at. You changed a helper or a route; the red belongs to a spec three files away, in a describe block written by someone who left the company before you arrived, in a file you may never have opened. A split pane only helps when the answer is already in view, and on old code the answer is usually in a file you are not looking at. No arrangement of panes fixes that. An address does: the run view names the failure, and one click puts you on the assertion line in the right file, whether you have ever opened it or not. The terminal told me a spec’s name and left the finding to me. The IDE just put me on the line.
It shows up in the small moments. The repo was improperly tested when I joined, so the work started with deciding what to test at all — risk-based, the parts of inherited code most likely to break, one spec at a time. Writing a test against code you don’t own used to be its own kind of flying blind: draft an assertion, fire the suite, then go read a terminal to find out whether the behavior you assumed was even there. With the caret, the answer lands on the line you just wrote. Run the one spec, read the mark — and you find out you’re wrong about someone else’s code while you’re still standing in it, before the context has cooled.
Deferral is what the old loop charged, and you don’t notice the tax until it is gone. The difference between wondering and knowing is not information. It is friction — and friction decides whether you act now or never.
Why Would You Ever Edit Blind?
Let the productivity argument go entirely; it misses the point by a level. What changed was the default.
When verifying costs a trip out of the file and back, editing blind is a rational time-saver, and on inherited code that is how you end up shipping changes you half-understand, hoping. When the check answers where you stand, blind stops being the rational default and becomes a choice you would have to justify. I edit code I don’t fully own more readily now — not braver, just not flying blind — because every step gets verified before the next one starts. The distance decides whether you ever dare change the code at all. That was the premise of this series. Here it is on a Tuesday.
Trust compounds the same way. Nothing about this arrived as one dramatic save. It is dozens of wonder-to-know cycles a day, each one a deposit, until the tool stops being something you evaluate and becomes something you lean on without checking. That is the middle’s job in this story: the bet from part one does not pay off in a single moment. It pays in repetition, in the accumulated certainty that the answer will be there when you look.
And the trust you build on ordinary days is the only trust that survives the extraordinary one. Nothing is on fire in this part of the story — that is the point. The day something is, you won’t have time to build trust. You will only spend what you built. That is the next part.