Skip to content

fix(web): make the small spinner actually small - #85

Merged
42-v merged 4 commits into
mainfrom
fix/ui-spinner-layer
Aug 26, 2026
Merged

fix(web): make the small spinner actually small#85
42-v merged 4 commits into
mainfrom
fix/ui-spinner-layer

Conversation

@42-v

@42-v 42-v commented Aug 26, 2026

Copy link
Copy Markdown
Owner

vault42-spinner-sm did nothing. It set w-4 h-4 as an @utility, which Tailwind v4 compiles into @layer utilities. .vault42-spinner is a plain rule setting w-5 h-5, and plain rules are unlayered — unlayered CSS beats layered CSS whatever the specificity. So the base overrode the modifier and every "small" spinner in the app rendered at the default size.

Nothing could see it. The class was in the markup, the rule was in the stylesheet, and the build emitted both. Only the layer differed.

Measured in the built CSS rather than argued:

rule offset layer size
.vault42-spinner-sm 10615 @layer utilities 16px
.vault42-spinner 43426 unlayered 20px
.vault42-spinner-lg 43737 unlayered 32px

vault42-spinner-lg was never affected — and only because somebody happened to write it as a plain rule beside the base rather than as an @utility. That is luck, not design, which is the actual finding here.

After the move, all three are unlayered and decided by source order: base (43343) → -sm (43654) → -lg (43737).

The gate holds the pairing, not the sizes, because the sizes were never the problem: a modifier and the class it modifies must be declared the same way, so whichever wins is decided by source order rather than by a cascade rule nobody was thinking about. It checks 3 pairs today and fails if it ever finds none. Mutation-verified — putting -sm back as an @utility fails it and names both forms:

vault42-spinner-sm is @utility and vault42-spinner is a plain rule.

Verified: 747 web tests pass; pnpm lint clean; go test ./tests/spec/ passes; coverage unchanged at 99.56 / 98.88.

fix(web): make the small spinner actually small

Comment thread web/src/__tests__/spinnerCascade.test.ts Fixed
@42-v
42-v force-pushed the fix/ui-spinner-layer branch from 07d6294 to 84e9471 Compare August 26, 2026 20:05
42-v added 4 commits August 27, 2026 00:21
vault42-spinner-sm did nothing. It set w-4 h-4 as an @Utility, which Tailwind
compiles into @layer utilities; .vault42-spinner is a plain rule setting w-5 h-5
and plain rules are unlayered. Unlayered CSS beats layered CSS whatever the
specificity, so the base overrode the modifier and every "small" spinner in the
app rendered at the default size.

Nothing could see it. The class was in the markup, the rule was in the
stylesheet, and the build emitted both -- only the layer differed. Measured in
the built CSS rather than argued: .vault42-spinner-sm landed at offset 10615
inside @layer utilities while .vault42-spinner sat unlayered at 43426.
vault42-spinner-lg was never affected, and only because somebody happened to
write it as a plain rule beside the base rather than as an @Utility.

The modifier now sits with its siblings, so the three are decided by source
order: base, then -sm, then -lg. Confirmed in the rebuilt CSS.

The gate holds the pairing rather than the sizes, because the sizes were never
the problem: a modifier and the class it modifies have to be declared the same
way. Mutation-verified -- putting -sm back as an @Utility fails it and names
both forms.
…ling

vault42-spinner-sm did nothing. It set w-4 h-4 as an @Utility, which Tailwind
compiles into @layer utilities, while .vault42-spinner was a plain rule setting
w-5 h-5 -- and a plain rule written outside any @layer is unlayered, which beats
layered whatever the specificity. Every "small" spinner rendered at the default
size. The class was in the markup, the rule was in the stylesheet, and the build
emitted both; only the layer differed.

All three are now @Utility rather than all three plain, which was the first
version of this fix. Both restore the 16px. Only this one also fixes the second
half: while .vault42-spinner was unlayered it silently beat 45 of the utilities
present in the bundle, so an ordinary h-4 or w-4 on a spinner did nothing
either. Measured after the change -- base at 9345, -lg at 10926, -sm at 11009,
all inside @layer utilities, so both modifiers win.

The gate compiles the stylesheet through Tailwind's own API and reads the
emitted offsets, because declaring both the same way turns out to be necessary
and not sufficient. Order inside @layer utilities is a property-set sort, not
source order. Adding w-full to vault42-btn-sm moves it ahead of vault42-btn and
makes it inert at all nine of its call sites, and nothing in the source looks
different -- the source-reading gate passes throughout, which is exactly what
that mutation demonstrates. That gate stays as the cheap early signal and its
comment now says what it does and does not hold.
CodeQL flagged the cascade gate's ruleOffset as incomplete sanitization, and it
was right even though every class name it sees is a literal from the table above
it: cls.replace(/[-]/g, ...) escapes exactly one metacharacter, which is the
shape of the finding whatever the input turns out to be.

Not building a pattern from a value is simpler than escaping one, so it scans
for the literal and checks the character after it. The delimiter check is what
stops .vault42-spinner matching inside .vault42-spinner-sm -- a class name ends
where the selector does. Same mutation still fails it.
@42-v
42-v enabled auto-merge (squash) August 26, 2026 22:21
@42-v
42-v force-pushed the fix/ui-spinner-layer branch from a20cc85 to 5397259 Compare August 26, 2026 22:21
@42-v
42-v merged commit b382fd5 into main Aug 26, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants