Every stylesheet change had gone through one person since 2022, which was never a policy and was always the outcome. The 2025 framework evaluation concluded that the problem was fluency rather than the code, and this is the year of doing something about it that was not adopting a framework.
The symptom
$ git log --format='%an' --since=2022-01 -- assets/css/
| sort | uniq -c | sort -rn
188 turker
4 alice
1 bob
$ git log --format='%an' --since=2022-01 -- src/
| sort | uniq -c | sort -rn
412 turker
388 alice
301 bobThree people write the application roughly equally and one person writes the stylesheet, which is a bottleneck with no technical cause. The five commits from the other two are all a colour value changed in a token file, which is the only part anybody felt able to touch.
Why it happens
A hand-written design system is better tailored and worse shared. Every decision in it is justified and none of them are discoverable, so the cost of a change is proportional to how much of the file somebody has read — and nobody had read it.
The fix
What 2025 tried
the framework evaluation concluded: the utility layer
is 518 of 2,100 lines and is the part a framework
replaces well — so take the naming convention and
decline the dependency.
done in February 2025, and the commit counts for the
rest of that year: turker 61, alice 2, bob 0.
so it helped with the utilities, which nobody had been
blocked on, and not with the components, which is where
the work is.Naming the utilities after a convention people already knew made the utilities discoverable and did nothing about the eight hundred and eighty lines of components. That is a correct fix aimed at the wrong quarter of the file, which was visible in the original analysis and had not been read carefully enough.
The component gallery
// a route behind an environment check, iterating a
// manifest of components and their states
foreach ( turkerdev_component_manifest() as $name => $states ) {
foreach ( $states as $label => $attributes ) {
printf( '<section data-component="%s"><h2>%s / %s</h2>%s</section>',
esc_attr( $name ), esc_html( $name ), esc_html( $label ),
turkerdev_render_component( $name, $attributes ) );
}
}
// the manifest, which is the actual artefact
return array(
'card' => array(
'default' => array(),
'no image' => array( 'image' => null ),
'long title' => array( 'title' => str_repeat( 'word ', 20 ) ),
'in a narrow slot' => array( '_wrapper' => 'width: 18rem' ),
),
// ...and the same shape for every other component
);
The states are the point rather than the components — a card in a narrow container, a button that is disabled, a title that wraps to three lines. Every one of those existed in the stylesheet and none of them existed anywhere a person could see without constructing the situation on a real page.
Documenting the layout primitives
four hundred lines that worked perfectly and were
opaque:
.stack vertical rhythm, one variable. the
selector is what nobody could read.
.cluster wrapping groups with even gaps, and a
negative margin superseded by gap in 2021.
.sidebar two columns, one intrinsic and one taking
the rest, collapsing at a container
threshold. four lines of flex arithmetic.
.grid-auto minmax auto-fit with one variable.
one page of prose, describing intent rather than
mechanism.The sidebar is four lines of flex basis and grow values that produce a specific collapse behaviour, and it is genuinely unreadable — the documentation says what it does and when to reach for it, and points at the original article for the mechanism. Explaining intent rather than implementation is what made these usable by somebody who was never going to derive the arithmetic.
Also deleted during the documentation pass: the cluster’s negative margin, superseded by gap in 2021 and never removed, cancelling a gap that no longer existed. Writing down what something does is the cheapest way to notice that part of it does nothing.
The lint rules, and the three that could not be written
rules: {
'declaration-property-value-disallowed-list': {
'/^(margin|padding|gap)/': [/^d/],
'/color$/': [/^#/, /^rgb/],
'font-size': [/^d/],
},
'selector-max-specificity': '0,3,0',
'no-important': true,
}
and the three that stayed as prose:
1 a component must not set margin on itself —
spacing is the parent's decision. a rule exists
and produces false positives on the utility layer,
which the linter cannot tell from a component.
2 a layout primitive takes exactly one variable.
not expressible: it is about the shape of the
abstraction rather than about syntax.
3 a colour comes from the palette OR is documented
as an optical exception. the first half is
enforced; the second is a comment convention.The first is the interesting failure — a working rule made unusable by a false positive rate on a layer the linter has no concept of. Scoping it to the components layer requires the tool to understand cascade layers, which it does not, and the workaround is a directory-based override that is close enough to be misleading.
Pairing on four changes
the expensive part, and the effective one: four
changes, one a fortnight, about 90 minutes each with
two people. the second person drives.
a new component (a pricing table)
a change to an existing one (the card, narrow)
a token change with a knock-on (the type scale)
a bug (a focus ring invisible on one background)
six hours over two months, and it is what actually
moved the commit counts.The gallery and the documentation made the codebase legible and neither of them produced a commit from anybody else — the pairing did. That is an uncomfortable finding because it is the least scalable intervention and it is the one that worked, and the artefacts are what made the six hours enough.
Verifying it worked
$ git log --format='%an' --since=2026-01 -- assets/css/
| sort | uniq -c | sort -rn
14 turker
11 alice
6 bob
$ ./bin/review-turnaround --paths=assets/css --since=2026-01
median 4h # was 2 days, waiting for one person
$ npx stylelint 'assets/css/**/*.css'
17 disables, all with a documented reason
$ npx backstop test --config=gallery
Passed: 88 Failed: 0 # every component × every stateThirty-one commits from three people where there had been one is the outcome, and the review turnaround dropping from two days to four hours is the second-order effect that nobody had counted as a cost. The visual regression suite now covers eighty-eight component states rather than forty-four pages, which is a better shape for the same tool.
What this costs
A gallery to maintain, which is a manifest that goes stale the moment somebody adds a component and does not add its states. Nothing enforces that — a test asserting every component in the theme appears in the manifest would, and it needs a component registry that does not exist.
The conventions are also now written down and therefore arguable, which is the point and is a change. A convention that lived in one person’s head was applied consistently and could not be discussed; a documented one gets exceptions requested, and two have been granted this year — both of them reasonable and both of them a small loss of coherence.