Originally published on ; this is the same problem one layer down.
The conclusion is not "review harder" — review is the wrong instrument for a failure that is invisible in the artifact being reviewed.
Guard the failure class, not the typo
The mistake I nearly made was to fix the line, commit, and move on. That fixes one instance of an unbounded class. The next #} in a stylesheet — or the next //, or the next stray } — lands exactly the same way, and the next debugging session starts from zero.
What went into the test suite instead was two assertions:
css = (STATIC_DIR / "style.css").read_text()
# 1. Every comment that opens must close.
assert css.count("/*") == css.count("*/")
# 2. No template-language comment syntax inside a stylesheet.
assert "#}" not in css and "{#" not in css
Two lines, no new dependencies, runs with everything else. It knows nothing about the line that broke; it knows the shape of the failure. That is the general move — write the assertion for the class, not the instance. "Is that one line correct?" is a test with a lifespan of one commit. "Do comment delimiters balance, and is foreign comment syntax absent?" still works after the file has been rewritten twice.
In this project the regression lives in test_aesthetic.py; the narrow run is:
pytest test_aesthetic.py -q -k css_comment_delimiters_balanced
That command proves only the text-level guard. The diagnosis has its own falsifier: if a real CSS parser still returns .timeline-row from the broken fixture above, then the rule was not swallowed by that comment and the investigation has to move to cascade, specificity, or asset selection. A rendered assertion such as “the timeline row computes to display: grid” is the stronger final gate because it observes the surface the user sees.
If you want more than two lines, the guards stack in cost order:
| Tier | Catches | Cost |
|---|---|---|
| Delimiter balance + foreign-token ban | unclosed comments, template syntax bleed | 2 lines, no dependencies |
| Parse the stylesheet, assert critical selectors survive | anything that removes a rule from the parse | one CSS parser dependency |
| Visual regression on key screens | everything, including cascade and specificity bugs | a browser in CI, plus baselines |
The middle tier is the one most projects skip and probably shouldn't. Parse the file with a real CSS parser and assert that the selectors your layout depends on are present in the parsed result. That would have failed loudly here: the swallowed rules were absent from the parse while present in the text — precisely the discrepancy that defines this bug.
Worth noting what would not have caught it: a linter. Stylelint and PostCSS parse this file without complaint, because the comment is closed, just later than intended. There is no rule for "this comment is longer than you meant it to be." Stylelint does ship no-invalid-double-slash-comments, which flags JS-style // comments in CSS — one member of this class, enumerated by hand. Tooling guards instances; you have to guard the class yourself.
Where these guards break
The two-line check is cheap, not perfect, and being clear about its limits is part of shipping it.
Delimiter counting has false positives, and balanced counts are not sufficient. A stylesheet containingcontent: "/*", or aurl()with those characters in it, fails a naive count. Two typos that cancel out still balance, and order is not checked: a*/before its/*counts the same.
The foreign-token ban is a blacklist. It knows about{#and#}because those are the languages in this project. A different stack brings different terminators, and the assertion won't know about them until someone adds them.
Neither assertion catches the sibling failure. A stale stylesheet in the browser cache produces an identical symptom — correct rules on disk, broken page — and no assertion about file contents can see it. That needs a version query on the asset URL, so the URL changes whenever the file does.
Those last two are why "tests green, screen broken" needs both suspects ruled out in order: is the browser reading this file, and is the browser seeing these rules as rules?
The rule I run on now
Absence of an error is not evidence of correctness in a recovery-by-design language. CSS, HTML, and most template languages swallow bad input and keep going, because that is what lets them survive version skew. There, the only ground truth is rendered output. A green suite tells you the markup exists and the routes return 200; it says nothing about whether the cascade produced the layout you asked for. If your verification surface never renders the thing, .
SOCIAL SHARE CARD GENERATOR