The obvious way to write a coverage ticket is: "Improve test coverage." One issue, whatever files happen to be thin, whoever picks it up figures out scope as they go. It reads fine. It also produces exactly one thing you can work on at a time, because the ticket doesn't say where one person's work ends and another's begins.
#102 didn't do that.
It started as a review, not a fix: run pnpm run test:coverage, list every
file dragging the aggregate down, and decide file by file whether the gap
was worth closing or worth accepting. The output was a table — src/browser.ts
at 0% statements, src/ascii/ansi.ts at 5.2%, the whole src/ascii/shapes/
directory averaging 13% — and instead of leaving that table as a to-do list
inside one issue, it became the spec for nineteen separate ones, each scoped
to a single file. No shared branch, no shared PR, no single person or
session who had to hold all nineteen files in their head at once to make
progress on any of them.
The filing timestamps are almost comically tight.
#104 (src/browser.ts)
went in at 17:17:41 UTC on 2026-08-26. Issue
#122
(src/ascii/shapes/state.ts), the last of the batch, went in at 17:18:06 —
twenty-five seconds later. In between: src/cli.ts, src/ascii/validate.ts,
src/xychart/layout.ts, src/layout-engine.ts, and every file in
src/ascii/shapes/ (circle, diamond, hexagon, rounded, rectangle, special,
stadium, in addition to state), each as its own numbered issue with its own
before-numbers table lifted straight from the coverage run. Nineteen
independent units of work, none of which referenced or blocked any other,
all traceable back to the same umbrella issue as their parent.
That shape is what makes "embarrassingly parallel" more than a turn of phrase here. A ticket that says "raise coverage on the ascii/ directory" has an implicit ordering problem the moment two people (or two agent sessions) pick it up at once — whoever finishes first has to rebase past the other's changes to the same files, or the two efforts silently duplicate work on the same function. A ticket scoped to exactly one file has no such problem, because two files' tests essentially never touch the same lines.
The close side of the ledger is just as tight, and just as informative
about mechanism, not just speed. Every one of the nineteen issues was
closed by its own pull request — test: improve coverage for src/browser.ts
closing #104, test: improve coverage for src/ascii/shapes/circle.ts closing
#115, and so on
through all nineteen — and every one of those PRs branched from main
under its own name:
test-coverage/src-browser-ts, test-coverage/src-ascii-line-utils-ts,
test-coverage/src-ascii-draw-bundles-ts. Nineteen branches, nineteen PRs,
zero shared diffs to conflict over. The first of them
(#123) merged at
17:51:10; the last
(#142) merged at
17:55:17. Four minutes and seven seconds covers all nineteen merges, and
the ordering inside that window is arbitrary — CI finishing, not
dependency, decided who landed first.
Every one of the nineteen chose the same fork in the road, too. Issue
#102's original ask was explicit about the alternative: write missing
tests, or add the file to vitest.config.ts's coverage exclude list with
a one-line justification if it wasn't worth testing directly. All nineteen
went with real tests, including the ones that looked like prime
exclude-list candidates on paper — src/browser.ts and src/cli.ts are
both thin entrypoints, and both got a real (if minimal) smoke test instead
of a shrug and an exclude rule.
One of the nineteen is worth a closer look, because it shows the shape
paying for itself in a way "improve coverage" never would have surfaced.
Issue #108 was
titled src/ascii/line-utils.ts. No such file exists in this repo — it
never has. What the PR that closed it
(#132) found was that
the coverage numbers cited in #108's own table (29.41% statements, 100%
branch, 66.66% functions) matched src/ascii/multiline-utils.ts exactly,
and documented that match explicitly in the PR description rather than
silently retitling the issue or guessing. The likeliest explanation is a
truncation artifact in the coverage table itself. Vitest's text reporter
clips long filenames from the left, and re-running
pnpm run test:coverage at f240616 — the commit the whole batch
branched from — still emits two rows rendered as the same string, in
two different directory groups:
src | 92.76 | 87.61 | 95.23 | 94.48 |
...line-utils.ts | 95.77 | 93.61 | 90.9 | 100 | 101,130,136
src/ascii | 79.12 | 63.08 | 87.07 | 80.67 |
...line-utils.ts | 29.41 | 100 | 66.66 | 28.57 | 54-81The first is src/multiline-utils.ts; the second is
src/ascii/multiline-utils.ts, and its 29.41/100/66.66 is exactly the
row #108 quoted. Read the group header plus the clipped cell as one path
and you get src/ascii/line-utils.ts — a file that has never existed.
Because the ticket was scoped to one file's worth
of numbers instead of a paragraph of prose about "the ascii utilities,"
the mismatch was checkable in one command (pnpm run test:coverage) and
fixable without anyone needing to first untangle which of several
plausible files the original report meant. A paragraph-length ticket
covering several files wouldn't have been wrong exactly, but nothing
about it would have forced anyone to actually run the numbers and notice
they pointed somewhere else.
The same close reading turns up one more correction worth being honest
about: the eight files under src/ascii/shapes/ run from
#115 through
#122 — circle,
diamond, hexagon, rounded, rectangle, special, stadium, and state — not
#115–#121. state.ts lives under shapes/ alongside the rest, easy to
undercount by one if you're going from memory of the umbrella issue instead
of the actual sub-issue list.
Issue #102 itself didn't close until 18:31:17 — exactly 36 minutes after
the last of the nineteen sub-issues merged — because closing it meant one
more step none of the individual file-level PRs owned: re-running
pnpm run test:coverage against the new baseline and raising the
repository-wide thresholds in vitest.config.ts to match, which happened
in #144, merged at
18:30:50.
That aggregate is the one number no individual ticket was accountable
for, so it's worth showing what it actually did. Checking out both ends
of the batch — f240616, the last commit before the first coverage PR
merged, and f2a234b, the merge of the last one, with nothing but those
nineteen merges in between — and running pnpm run test:coverage at each:
f240616 — the last commit before the first coverage PR merged
Statements : 78.86% ( 5529/7011 )
Branches : 67.92% ( 2736/4028 )
Functions : 83.66% ( 502/600 )
Lines : 80.71% ( 4990/6182 )
f2a234b — the merge of the nineteenth
Statements : 90.41% ( 6339/7011 )
Branches : 80.26% ( 3233/4028 )
Functions : 95.16% ( 571/600 )
Lines : 92.08% ( 5693/6182 )The denominators don't move — 7011 statements, 4028 branches, 600 functions, 6182 lines at both ends. Nineteen PRs that only added tests changed nothing about how much code there was to cover; they changed how much of it the suite actually walked through.
The three worst entries on #102's review table went the whole distance:
src/browser.ts from 0% statements to 100%, src/ascii/ansi.ts from
5.2% to 100%, and src/ascii/shapes/ as a directory from 15.53% to
99.15%. The sliver left in shapes/ is corners.ts and index.ts —
the two files in there the review table never listed, and so the two
nobody filed a ticket for. (#102's own table says 13.14% for that
directory; it was generated at 043af31, a few merges earlier, which
still reproduces its cited aggregate of 78.74/67.91/83.22/80.58
exactly.)
That ordering isn't incidental. The per-file tickets were deliberately scoped so that finishing all of them didn't automatically finish the umbrella — someone still had to look at the aggregate result and decide the new floor was real, not just eyeball nineteen individually green PRs and assume the whole was better than the sum of its parts.
None of the individual test files here are interesting on their own — a
Canvas-based smoke test for drawMultilineTextCentered, a handful of
edge cases for src/ascii/ansi.ts's color codes. What's reusable is the
backlog shape: an umbrella issue that does the reviewing (what's low,
what's worth fixing, what's acceptable to exclude) and stops there, plus
one child issue per unit of work small enough that two people picking up
two different children can never step on each other. That's the same
principle behind sweeping a backlog oldest-first across a fleet of
isolated worktree sessions rather than one long-running branch touching
everything at once — the granularity is what makes the parallelism safe,
not any cleverness in the individual fixes. Nineteen files, filed in
twenty-five seconds, closed inside a single evening, is what that
principle looks like when you actually measure it instead of just claiming
it.