Skip to content

fix(ui): a hover that never moved anything, and a selection that vanished under the pointer - #21

Merged
meocong merged 1 commit into
masterfrom
ui/lift-actually-lifts
Aug 14, 2026
Merged

fix(ui): a hover that never moved anything, and a selection that vanished under the pointer#21
meocong merged 1 commit into
masterfrom
ui/lift-actually-lifts

Conversation

@meocong

@meocong meocong commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Review of the two interface changes that just landed (#19, #20), done against the browser rather than against the source. Three things were doing nothing, and the same reason runs through all three: a class cannot outrank an inline style, and Tailwind's ring is not a ring.

.lift moved nothing

It set transform: translateY(-2px), and both cards it is on are Motion elements. Motion animates by writing transform into the inline style, which no class can beat. Worse, the transition: transform beside it did apply to the inline transform Motion rewrites every frame — so the library's entrance animation had a 180 ms lag chasing itself.

Moved to the translate property: its own slot, composes with whatever transform holds, and Motion never touches it. Measured after the change:

recent card:  translate="0px -2px"   (plain button)
chosen agent: translate="0px -2px"   (Motion element)

The chosen agent lost its outline under the pointer

.lift:hover sets box-shadow, which beat the selected card's shadow-[0_0_0_1px_accent] — so pointing at the selected agent deselected it visually.

The first attempt was ring-1 ring-accent, and the browser said no: Tailwind v4 implements a ring as a box-shadow, and the computed style came back with the same two shadows and no accent anywhere. outline is a separate property and survives both:

chosen at rest:  shadow=none                        outline=rgb(15, 115, 80) solid 1px
chosen hovered:  shadow=rgba(28, 25, 23, 0.1) 0px…  outline=rgb(15, 115, 80) solid 1px

Ticker re-counted on every change

Its own doc says a number that changes because the user filtered the screen should snap, because they are comparing it against what was there a moment ago. The code counted from zero every time, which on the analytics range switch turns a comparison into a wait. Counted once now, with the flag set on completion rather than on start so React's development double-invoke does not eat the only run.

Also

Recent's cards were lifting through whileHover={{ y: -2 }} — a spring and a rAF loop per hovered card, to move something two pixels. They use the same .lift as everything else now, which costs nothing while the pointer is elsewhere.

The token test grew outline- alongside text-, bg-, border- and ring-, since that is now a colour this interface names.

Verification

Full gate on this tree: cargo fmt --check, clippy --workspace --all-targets --features bundled -D warnings, 1312 Rust tests, 298 web tests, all 18 browser suites, and 44 screenshots in both schemes — no overflow, contrast AA everywhere.

🤖 Generated with Claude Code

…shed under the pointer

Reviewing the two interface changes that just landed, against the browser rather than
against the source. Three of them were doing nothing, and the same reason runs through all
three: a class cannot outrank an inline style, and Tailwind's `ring` is not a ring.

**`.lift` moved nothing.** It set `transform: translateY(-2px)`, and both cards it is on are
Motion elements — Motion animates by writing `transform` into the inline style, which no
class can beat. Worse, the `transition: transform` beside it *did* apply to the inline
transform Motion rewrites every frame, so the library's entrance animation had a 180 ms lag
chasing itself. Moved to the `translate` property: its own slot, composes with whatever
`transform` holds, and Motion never touches it. Measured after the change —
`translate: 0px -2px` on hover, on a plain button and on a Motion element alike.

**The chosen agent lost its outline under the pointer.** `.lift:hover` sets `box-shadow`,
which beat the selected card's `shadow-[0_0_0_1px_accent]`, so pointing at the selected
agent deselected it visually. The first attempt was `ring-1 ring-accent`, and the browser
said no: Tailwind v4 implements a ring *as* a box-shadow, and the computed style came back
with the same two shadows and no accent anywhere. `outline` is a separate property and
survives both. Confirmed at rest and on hover: `rgb(15, 115, 80) solid 1px` in each.

**`Ticker` re-counted on every change.** Its own doc says a number that changes because the
user filtered the screen should snap, because they are comparing it against what was there a
moment ago — and the code counted from zero every time, which on the analytics range switch
turns a comparison into a wait. Counted once now, and the flag is set on completion rather
than on start so React's development double-invoke does not eat the only run.

Also: `Recent`'s cards were lifting through `whileHover={{ y: -2 }}` — a spring and a rAF
loop per hovered card, to move something two pixels. They use the same `.lift` as everything
else now, which costs nothing while the pointer is elsewhere.

The token test grew `outline-` alongside `text-`, `bg-`, `border-` and `ring-`, since that is
now a colour this interface names.

Full gate on this tree: `cargo fmt --check`, `clippy --workspace --all-targets --features
bundled -D warnings`, 1312 Rust tests, 298 web tests, all 18 browser suites, and 44
screenshots in both schemes with no overflow and contrast AA everywhere.
@meocong
meocong merged commit ff234af into master Aug 14, 2026
10 checks passed
@meocong
meocong deleted the ui/lift-actually-lifts branch August 14, 2026 23:57
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.

1 participant