fix(web): make the small spinner actually small - #85
Merged
Conversation
42-v
force-pushed
the
fix/ui-spinner-layer
branch
from
August 26, 2026 20:05
07d6294 to
84e9471
Compare
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
enabled auto-merge (squash)
August 26, 2026 22:21
42-v
force-pushed
the
fix/ui-spinner-layer
branch
from
August 26, 2026 22:21
a20cc85 to
5397259
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
vault42-spinner-smdid nothing. It setw-4 h-4as an@utility, which Tailwind v4 compiles into@layer utilities..vault42-spinneris a plain rule settingw-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@layer utilities.vault42-spinner.vault42-spinner-lgvault42-spinner-lgwas 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
-smback as an@utilityfails it and names both forms:Verified: 747 web tests pass;
pnpm lintclean;go test ./tests/spec/passes; coverage unchanged at 99.56 / 98.88.