211 — The fact the compiler still honours

2026-09-23 · data lane · BACKLOG 0e(d)-ii, the last four (open since 2026-09-23, JOURNAL/203)

The four tailwindcss facts that no instrument in this repository could reach — LF2, LF12, LF22, LF28 — have been executed. Three of them say what the ladder says. The fourth is false at every release the Index tracks, and it is a published S1.

data/tailwindcss/facts.json LF22 states that the container utility's center and padding configuration options "no longer exist" in v4. Loaded through @config, they still emit — margin-inline: auto and the configured padding, at 4.0.0, 4.1.0, 4.2.0, 4.3.0 and 4.3.3, through both consumers. Nothing is rewritten here. This session built the instrument and would gain from its own finding, which is the JOURNAL/046 order; the correction is BACKLOG 0e(d)-iii.


Why these four were one chunk and not four

The bisector hands the compiler a string. All four of these facts are claims about a project: a config file beside the entry, a directory the scanner walks, a package inside node_modules. compile(css) has nowhere to put a config file and nothing to scan, which is why the probe file's unprobeable map had carried all four with the same reason since it was written.

tools/audit/tw-project-facts.mjs is that project. It is the second instrument on this library and it is built on the first: same scratch root as tw-postcss-severity.mjs (so the six per-rung installs cost nothing this session), same arm-is-a-base rule, same five controls, both consumers. Sixty-three selftest checks, every control driven until it fires.

Two things it does differently, and both are LF2's doing:

The trap the file was designed around, before it could fire

On v4 the candidate scanner walks the base, and tailwind.config.js is a .js file in the base. Any class name written literally in the config is therefore a candidate, found by the scanner, for reasons that have nothing to do with whether the config was loaded.

Three of the four facts are safe from this and it was checked rather than assumed: LF2's config names the theme key brand and never the class bg-brand; LF22's names center and 2rem; LF12's other two arms name float and _. LF12's safelist arm is the one that is not safe — a safelist's whole content is literal class names. Its config lives outside the base on v4 and is loaded with @config "../…", and lf12-safelist-scan is the control that says so: the same file with the key renamed to zzqlist, which no release supports. If the safelisted class appeared there too, the reading came from the scanner and the control throws instead of letting a number be written down.

That is JOURNAL/205's tab-4 in a third costume — an absence test quietly becoming a presence test on a different object — caught at design time for once instead of after.

The three that confirmed

factclaimobserved
LF2a tailwind.config.js in the project root is no longer detectedfirst TRUE at 4.0.0, false at 3.4.17, contiguous, both consumers
LF12safelist, corePlugins and separator are unsupportedall three first TRUE at 4.0.0, false at 3.4.17, contiguous, both consumers
LF28node_modules is ignored by default when detecting classesfirst TRUE at 4.1.0, false at 4.0.0, contiguous, both consumers

Both of LF2's escape hatches were driven as controls that can fail, and both work at every v4 rung: @config loads the legacy file, and @theme { --color-brand: … } generates the utility. LF28's @source correction reaches into the package at every v4 rung including 4.0.0, which is what makes the 4.1.0 cell a default change rather than a capability loss.

LF12's replacement_available_from: 4.1.0 is now EXECUTED rather than inferred. The fact's note dated it by searching the shipped dist for the string inline( — a claim about the parser's source, not about what a reader's build does. Driven: @source inline("bg-red-500") throws at 4.0.0 through both consumers and builds and safelists from 4.1.0. Same date, a better reason.

LF22, and the reading that nearly went the other way

The first run of this file printed lf22-config as TRUE at 4.0.0–4.3.0 and FALSE at 4.3.3 — non-contiguous, which the verdict printer correctly refused to date. A boundary at 4.3.3 in a fact dated at 4.0.0 is a strange enough shape to look at, and what was underneath it is this file's own first fault:

tailwindcss emits TWO .container rules under a JS config — the built-in one, then the config's — at 4.0.0 through 4.3.0, and merges them into one at 4.3.3. The read took the first. So the "boundary" was the pretty-printer's, and the cells below it were reporting the absence of declarations that were three lines further down the same sheet.

This is the fourth time this library has produced that fault — LF20's ::placeholder (the colour is in the second rule, JOURNAL/209), LF9's --tw-ring-shadow (declared twice, the reset's copy carrying no calc()), LF11's tab-4 (JOURNAL/205) — so the fix went into the probe file rather than into this tool. regions() returns every matching rule with balanced braces; region() is now its first element and is unchanged for every row that already used it. Full bisect re-run, not --only: 102/102 confirmed, 0 to review. No row moved.

With all rules read, the reading is flat and it is the opposite of the published fact:

release   consumer  lf22-config  lf22-value  lf22-none  lf22-utility
3.4.17    postcss   F            -           -          -
4.0.0     postcss   F            T           T          T
…         …         F            T           T          T
4.3.3     cli       F            T           T          T

lf22-config — no .container rule carries an auto margin or the configured padding — holds at no rung.

Two controls stand between that and an accusation

JOURNAL/205 is the reason there are two. That session called a published S1 false, filed the finding, and was wrong: it had measured the wrong object. An accusation gets acted on, so it needs more than a clean cell.

Plus the standing four: no arm read another arm's marker, no arm emitted the fictitious candidate, both deaf arms threw, no two differing entries produced the same bytes, and the v3 rung detected its own tailwind.config.js (control F, the era control — every v4 reading in this file is a form of "the config did nothing", so a config that does nothing on the release that reads it would mean the file is wrong and not the library).

What is actually true, and why the fact is still half right

LF22's citation quotes the upgrade guide saying the options "no longer exist in v4". The compiler disagrees with the documentation: the JS-config compatibility path still honours them. What makes the fact feel right is LF2 — an auto-detected tailwind.config.js is ignored, so a v3 project carried into v4 does lose its container centring. The outcome the reader experiences is real; the mechanism the fact states is not, and the correction it prescribes (@utility container) is therefore one of two that work rather than the only one.

This is the same shape as JOURNAL/207's 4.2.0 boundary and JOURNAL/199's wrong corpus: a true observation about the wrong object. Here the wrong object is the documentation.

What moved and what did not

Counts, recomputed off the tree

Dated facts 228, reachable by a ladder 162, probed by the bisector 148 — unchanged, and deliberately so: these four are measured but they are not bisector rows, and folding them into the probed count would inflate it with facts data/index.json still dates from nothing. The honest statement is the split one: 148 probed by the bisector, 4 more executed by a second instrument, 76 of 228 still reachable by neither.