Files
sim/packages
Waleed 806ea0c5bb fix(tooltip): stop text blurring for ~150ms every time a tooltip appears (#6211)
* fix(tooltip): remove velocity skew/scale that blurred text on every appear

The tooltip animated a fractional scale() + skew() over 150ms on the element
containing its text. Chrome promotes the bubble to a compositor layer for the
transition, rasterizes the text once at the pre-transition scale, then
GPU-resamples that bitmap for the duration — so text rendered blurry until the
transition settled and the layer re-rasterized at 1:1.

It fired on every appear: a pointer entering a trigger is by definition moving,
so the first pointermove after pointerenter always set a non-zero skew and a
fractional scale.

- drop the velocity-reactive skew/scale flourish and the pointer-velocity
  bookkeeping that existed only to feed it
- round tooltip position to whole pixels; clientX/clientY are fractional on
  HiDPI/zoomed displays, leaving the bubble on a subpixel boundary
- drop the dead `filter` from the transition list — nothing ever set a filter
- skip the state update when the rounded position is unchanged, so pointer
  jitter no longer re-renders every Tooltip.Trigger/Content consumer

The 150ms ease-out translate is kept, so the bubble still trails the cursor.

* improvement(tooltip): keep the velocity flourish, drive it without a CSS transition

Restores the velocity-reactive skew/scale removed in the previous commit. The
flourish was never the problem on its own — handing it to a CSS transition was.
An interpolated fractional scale makes the compositor rasterize the tooltip's
text once and resample that bitmap for the duration, which is what read as blur.

Applied as a static value per pointer event instead, so every frame is
rasterized at its own scale:

- split the transform across the individual `translate`, `scale`, and
  `transform: skew()` properties, and transition only `translate` — position
  still eases toward the cursor, the flourish no longer interpolates
- smooth the pointer velocity in JS (low-pass filter) to replace the smoothing
  the CSS transition used to provide, so the squish still ramps rather than
  snapping between raw per-event velocities
- quantize the flourish to 3 decimals so jitter below the visible threshold
  settles instead of re-rendering every consumer

Whole-pixel position rounding and the redundant-update bail-out are unchanged.

* fix(tooltip): don't seed pointer velocity from the trigger box on focus

The previous commit routed `onFocus` through a shared reveal helper that seeds
`lastPointerRef` from the coordinates it is given. For focus those are the
trigger's box center, not the pointer — so if the pointer already happened to be
over the trigger, the next `pointermove` measured the box-to-cursor delta as
velocity and spiked the skew/scale flourish.

Split the helper in two: reveal-from-pointer seeds velocity tracking,
reveal-from-element leaves it cleared. Restores the pre-PR behavior, where focus
explicitly nulled the pointer snapshot.

Caught by Cursor Bugbot.

* fix(tooltip): make the flourish smoothing frame-rate independent

The velocity low-pass filter applied a fixed coefficient per pointer event, so
how fast the squish settled depended on how fast the device emitted events —
233ms at 30Hz down to 29ms at 240Hz, an 8x spread for the same gesture. It was
also far snappier than the 150ms CSS ease-out it replaced, so the flourish read
as twitchier than before.

Derive the coefficient from the real elapsed time instead
(1 - exp(-dt / tau), tau = 50ms). Settling is now flat at ~150ms from 60Hz
upward, matching the duration of the transition this stands in for.

Also separates the smoothing delta from the velocity-normalization delta: the
latter is still floored at one frame to keep a 1ms event from reporting an
enormous velocity, but flooring the former was itself a source of frame-rate
dependence below 16ms.

Verified against Chrome's documented re-raster behavior: a layer is re-rastered
at its new scale when the scale changes via script, but not when a declarative
animation interpolates it, which is why the flourish must stay out of the
transition list.
https://developer.chrome.com/blog/re-rastering-composite
2026-08-03 11:37:23 -07:00
..

Packages

Internal

Package Description
@sim/tsconfig Shared TypeScript configs (base, nextjs, library, library-build)
@sim/db Database schema and Drizzle ORM utilities
@sim/logger Structured logging with colored output
@sim/testing Test factories, builders, and assertions

Published

Package npm Description
cli simstudio Run Sim locally via Docker
ts-sdk simstudio-ts-sdk TypeScript SDK for workflow execution
python-sdk simstudio-sdk Python SDK for workflow execution