This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>
8.9 KiB
Motion Driver Tooltip Position Jump Bug
Problem Summary
When using animatePosition with a shared tooltip (scope pattern) in the motion driver, rapidly moving between triggers causes the tooltip to JUMP to a wrong position (often far left or near origin) before animating back.
Reproduction Pattern
- Hover on rightmost button (HIRE US), wait ~1 second for tooltip to fully appear
- Move mouse FAST to the left, briefly hitting the middle button (BENTO)
- Within ~33ms (1 frame at 30fps), continue to the leftmost button (TAKEOUT)
- The tooltip JUMPS to wrong position then animates back
Video Analysis (CleanShot 2026-01-22 at 22.46.02.mp4)
- Frame rate: 30fps (~33ms per frame)
- Frame 50: User leaves the left button (TAKEOUT), moving right
- Frame 56: Tooltip correctly positioned below middle button (COPY-PASTE UI / BENTO), shows "Bento — Free + paid pre-made UI"
- Frame 57: THE JUMP - Tooltip jumps to wrong position overlapping the buttons, text changes to "Takeout — universal RN starter kit"
- Jump characteristics: ~100px UP in Y, shifted left in X, happens in exactly 1 frame
Timing Constraints
- Total movement time: ~50-150ms for the bug to trigger
- Key factor: Brief hover on middle button (~33ms) then immediately to left button
- The bug seems to occur when animation is interrupted mid-flight by rapid position changes
Affected Code
/pkgs/core/animations-motion/src/createAnimations.tsx- Motion driver implementation/pkgs/ui/popper/src/Popper.tsx- PopperContent handles position with floating-uiflushAnimation()- handles animation updatesgetDiff()- computes what properties changed
Files for Reproduction
/apps/gui.hanzo.ai/features/site/home/PromoLinksRow.tsx- Original component with the bug/apps/kitchen-sink/src/usecases/TooltipPositionJumpCase.tsx- Test case/apps/kitchen-sink/scripts/repro-tooltip-jump.ts- Playwright reproduction script
Jump Detection Criteria
- dx > 300px or dy > 300px in a single frame
- Jump from reasonable position (x > 100, y > 100) to near origin (x < 50 or y < 50)
Previous Fix Attempts
- Parsed matrix transform to find X/Y - broke other animations (immediate movement instead of animated)
- Issue: Hard to distinguish between legitimate position changes and bug jumps
Hypothesis
The motion driver's animation state gets out of sync when:
- Animation A starts (moving to middle button position)
- Before A completes, animation B starts (moving to left button position)
- The animation system calculates diff from wrong base position (possibly 0,0 or stale value)
- Result: Large jump because diff is computed against wrong starting point
Testing Notes
- Bug is motion-driver specific - other drivers (css, native, reanimated) may not have this issue
- Run kitchen-sink on port 9000:
yarn start:web - Run hanzogui.dev on port 8282:
yarn dev --port 8282(v2 branch) - Animation driver can be changed via URL param or config
Playwright Reproduction Results (SUCCESS!)
Script: /apps/kitchen-sink/scripts/repro-tooltip-jump.ts
Attempt 2 detected 2 jumps:
(0,-4) -> (609,162)delta:(609,166) - Tooltip appearing (expected, from initial position)(609,166) -> (106,36)delta:(503,130) - THE BUG! Jump from correct position to near-origin
The second jump is the bug - tooltip goes from reasonable position (609,166) to near-origin (106,36) in one frame.
Analysis So Far
The Jump Pattern
- Consistent reproduction:
(609,166) -> (~127-195, ~42-65) - Jump goes TO LEFT and UP (toward origin direction)
- Target X (~127-195) is LESS than leftmost button X (393)
- This suggests animation is computing from WRONG starting position
Code Flow
flushAnimation()is called when position changesgetDiff()computes changed properties (e.g.,{ x: newValue })animate(scope.current, diff, animationOptions)animates element- Motion library reads current transform and animates TO the diff target
Hypothesis: Transform State Corruption
When rapidly changing triggers:
- Animation A starts: x 609 → 537 (HIRE → BENTO)
- Before A completes, Animation B starts: x → 393 (to TAKEOUT)
- Motion reads current transform which may be in corrupted/transitional state
- Animation animates from wrong value causing visual jump
Attempted Fixes
controls.current.complete()before new animation - didn't help (bundled server issue)- Need to verify fix actually reaches browser
Debugging Challenge
- hanzogui.dev on 8282 uses bundled code, doesn't pick up local changes
- kitchen-sink on 9000 doesn't trigger tooltips in headless mode
- Need to either fix bundling or find different test approach
Key Discovery - Transform Jump Pattern
From Playwright reproduction, the transform jumps like this:
Before: matrix(1, 0, 0, 1, 609, 166)
After: matrix(1, 0, 0, 1, 128, 43)
This happens in ONE FRAME, not animated. The motion library's animate() is supposed to animate from the current position to the target, but it's snapping instantly.
Root Cause Analysis
The jump happens when:
- Animation A is running (tooltip moving from trigger 1 to trigger 2)
- Animation B starts immediately (tooltip should move from trigger 2 to trigger 3)
- Motion's animate() gets called with just the DIFF (new target values)
- BUT the animation starts from the WRONG position (possibly 0,0 or stale value)
Potential causes:
- Motion library reads stale transform when animation is in flight
- Something clears/resets the element's transform before motion reads it
- The
controls.current.stop()or animation interruption isn't working correctly - Race condition between animation frames and new animation start
Files Involved
-
/pkgs/core/animations-motion/src/createAnimations.tsx- Motion driverflushAnimation()- Called when position changesgetDiff()- Calculates what properties changedanimate(scope.current, diff, animationOptions)- Starts animation
-
/pkgs/ui/popper/src/Popper.tsx- PopperContentanimatePositionprop enables x/y animation- Passes x/y from floating-ui to the animated element
TODO
- Reliably reproduce with Playwright script (1 iteration = 1 jump)
- Verify other drivers don't have this issue
- Add debug logging to trace values during jump
- Test with
controls.current.stop()before new animation (implemented) - Consider using motion's
useMotionValuefor x/y instead of animate() - Fix without breaking normal animations
- FINAL STEP: Create standalone reproduction repo and open PR on https://github.com/motiondivision/motion (n8 knows matt the author, highly responsive to thorough repros)
FINAL FIX (in createAnimations.tsx)
The fix handles position animation interruptions by:
- Detecting when a transform with translate is changing
- Reading the current animated position BEFORE calling stop() (important - stop() resets it!)
- Using keyframes to explicitly animate FROM current position TO target
const hasTransformWithTranslate =
typeof diff.transform === 'string' && diff.transform.includes('translate')
if (hasTransformWithTranslate && controls.current) {
// IMPORTANT: Read transform BEFORE stopping - stop() may reset it!
const computedStyle = getComputedStyle(node)
const currentTransform = computedStyle.transform
controls.current.stop()
// set transform to captured value to prevent flicker
node.style.transform = currentTransform
node.offsetHeight // force reflow
if (currentTransform && currentTransform !== 'none') {
// extract current position from matrix(1, 0, 0, 1, tx, ty)
const matrixMatch = currentTransform.match(
/matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
)
if (matrixMatch) {
const currentX = parseFloat(matrixMatch[1])
const currentY = parseFloat(matrixMatch[2])
// build start transform from current animated position
const startTransform = `translateX(${currentX}px) translateY(${currentY}px)`
const targetTransform = diff.transform as string
// use keyframes to animate from current position to target
const keyframeDiff = { ...diff }
keyframeDiff.transform = [startTransform, targetTransform]
controls.current = animate(scope.current, keyframeDiff, animationOptions)
return
}
}
}
Key Insights
-
Gui converts x/y props to CSS transform: When you use
x={100}, Gui converts it totransform: translateX(100px)before passing to the motion driver. -
motion's stop() resets the transform: When you call
controls.current.stop(), the element's transform may reset tomatrix(1, 0, 0, 1, 0, 0). You MUST read the current position BEFORE calling stop(). -
Keyframes work for smooth interruption: By using
transform: [startTransform, targetTransform]as keyframes, motion knows exactly where to start from and animate to, preventing jumps.
Motion Version
- Upgraded to motion 12.29.0 (from 12.28.1) on 2026-01-22