Skip to content

fix(#838): buttons in the house themes never lifted or pressed - #865

Merged
TortoiseWolfe merged 1 commit into
mainfrom
fix/838-button-states-never-render
Aug 20, 2026
Merged

fix(#838): buttons in the house themes never lifted or pressed#865
TortoiseWolfe merged 1 commit into
mainfrom
fix/838-button-states-never-render

Conversation

@TortoiseWolfe

Copy link
Copy Markdown
Owner

The bug

Buttons on all three ScriptHammer themes never lifted on hover and never pressed on active. Both state rules were out-specified by the resting rule and had never applied — all three states resolved to --sh-plate.

Specificity, not a typo

resting  chain                                    (0,7,0)
hover    .btn:hover                               (0,3,0)
active   chain-minus-two + :active                (0,6,0)

:not() contributes the specificity of its argument, so the five-:not() chain on the resting rule scores 7 and both neighbours lose to it. The chain was added by #379 so this block would stop beating the sh-* utilities — and it silently disabled the two rules underneath it.

Each state now repeats the whole chain, giving a strict ladder 7 < 8 < 9. Verbose on purpose: doing it by counting rather than by source order means re-ordering the file cannot swap the states back.

The literal black that survived nine tickets

The hover rule read rgb(0 0 0 / 0.3) — invisible on a dark surface, grey sludge on a saturated one, and precisely what the ink tokens exist to replace. It survived the entire #376/#426 epic because it was never rendered: no screenshot can show a state that doesn't apply, and no reviewer can see one.

New --sh-plate-lift is built from the same --sh-ink-* tokens as --sh-plate, just raised further.

The gate couldn't see it either

depth-tokens.spec.ts read resting only — every test in it. Added T5: for each house theme, hover and press a real .btn, assert the three states are three different things, and that none uses literal black. It asserts difference rather than exact values, so a legitimate design tweak doesn't break it while the ladder collapsing does.

Mutation-verified: sinking the hover selector back to .btn:hover fails with scripthammer-light: hover must differ from resting.

Two things my first draft got wrong

It read the shadow once, straight after .hover(). DaisyUI transitions box-shadow, so that catches the animation mid-flight — measured 7.05px 14.11px -5.26px, partway between --sh-plate (6/12/-5) and --sh-plate-lift (10/20/-6). A one-shot read is timing-dependent and could land equal to resting and fail for nothing. It polls now — three consecutive runs: 7 passed, 7 passed, 7 passed.

That same truncated read briefly made it look like the fix hadn't worked, because the intermediate value shares its leading offsets with the resting shadow. I confirmed the rule actually wins by enumerating the matching CSSOM rules — exactly one .btn:hover rule carries a box-shadow, and it's ours, resolving to var(--sh-plate-lift).

Verification

Measured after the fix on all three house themes: resting != hover != active, on every one. Before, all three were byte-identical.

lint clean, 4730 vitest tests, 428 script tests, tsc --noEmit clean.

This is a visible change to every button on three themes. The gate proves the states now differ; it can't tell you the lift looks right. Worth an eyeball on the preview before you take my word for the aesthetics.

Closes #838 — and this is the named exit condition of epic #426.

🤖 Generated with Claude Code

Both state rules were out-specified by the resting rule and had never applied.
On all three ScriptHammer themes, hover and active resolved to the resting
`--sh-plate`: buttons did not lift, and they did not press.

SPECIFICITY, NOT A TYPO

  resting  chain                                  (0,7,0)
  hover    .btn:hover                             (0,3,0)
  active   chain-minus-two + :active               (0,6,0)

`:not()` contributes the specificity of its ARGUMENT, so the five-`:not()` chain
on the resting rule scores 7 and both neighbours lost to it. The chain was added
by #379 so this block would stop beating the `sh-*` utilities, and it silently
disabled the two rules underneath it.

Each state now repeats the whole chain, giving a strict ladder 7 < 8 < 9. It is
verbose on purpose: doing it by counting rather than by source order means
re-ordering this file cannot swap the states back.

THE LITERAL BLACK THAT SURVIVED NINE TICKETS

The hover rule read `rgb(0 0 0 / 0.3)` — invisible on a dark surface, grey sludge
on a saturated one, and exactly what the ink tokens exist to replace. It survived
the whole #376/#426 epic because it was never rendered: no screenshot can show a
state that does not apply. New `--sh-plate-lift` is built from the same
`--sh-ink-*` tokens as `--sh-plate`, just raised further.

THE GATE COULD NOT SEE IT EITHER

`depth-tokens.spec.ts` read RESTING only — every test in it. Added T5: for each
house theme, hover and press a real `.btn` and assert the three states are three
different things, plus that none of them uses literal black. It asserts
DIFFERENCE rather than exact values, so a legitimate design tweak does not break
it while the ladder collapsing does.

MUTATION-VERIFIED: sinking the hover selector back to `.btn:hover` (0,3,0) fails
with "scripthammer-light: hover must differ from resting".

ONE THING THE FIRST DRAFT GOT WRONG. It read the shadow once, straight after
`.hover()`. DaisyUI transitions box-shadow, so that catches the animation
MID-FLIGHT — measured `7.05px 14.11px -5.26px`, partway between --sh-plate
(6/12/-5) and --sh-plate-lift (10/20/-6). A one-shot read is timing-dependent and
could land on a value equal to resting and fail for nothing. It polls now. Run
three times back to back to confirm: 7 passed, 7 passed, 7 passed.

That same truncated one-shot read is also what briefly made it look like the fix
had not worked at all — the intermediate value shares its leading offsets with
the resting shadow. Confirmed the rule really wins by enumerating the matching
CSSOM rules: exactly one `.btn:hover` rule with a box-shadow, and it is ours.

Measured after the fix, all three themes: resting != hover != active.

lint clean, 4730 vitest, 428 script tests, tsc clean.

Closes #838

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TortoiseWolfe
TortoiseWolfe merged commit a8f7832 into main Aug 20, 2026
37 of 38 checks passed
@TortoiseWolfe
TortoiseWolfe deleted the fix/838-button-states-never-render branch August 20, 2026 20:08
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.

Buttons in the three house themes never lift or press — both state rules are out-specified by the resting rule

2 participants