fix(GestureMenu): Trace should above the GestureMenu blur#4466
fix(GestureMenu): Trace should above the GestureMenu blur#4466cindmichelle wants to merge 44 commits into
Conversation
…ghlightColor props Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…el/Cheatsheet gap (cybersemics#3709) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eDiagram.onRef Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes regression from commit 120a77e where the per-segment rendering refactor split path handling into useGradient-dependent branches, causing two issues: 1. Arrowhead added to help gesture (rdld): The markerEnd condition lost the `path !== 'rdld'` guard, adding an unwanted arrowhead to the ? gesture in the test snapshot. 2. Straight lines instead of Bezier curves: The rdld Bezier path override was only added to the `!useGradient` branch, so callers using useGradient=true (default, e.g. CommandItem) rendered straight line segments instead of the smooth ? shape in the Help modal. Both fixes restore the original behavior by re-adding the rdld special cases to the per-segment rendering path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminate double negative by renaming to a positive boolean.
glow={true} (default) enables the glow, glow={false} disables it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
More descriptive name since it's specifically an item inside the Gesture Menu, not a generic gesture item. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clarifies that Cancel and Command Universe are always-visible commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#3791) Decouple the gesture menu's ProgressiveBlur from PopupBase and place it on a new `gestureMenuBlur` z-index, just below `gestureTrace`, so the trace paints above the blur — darkened by the menu overlay but no longer blurred. Since the blur no longer inherits its height from the popup, derive its height from the command count via shared rem constants; share the bottom-tail constant with the FadeTransition wrapper's paddingBottom so the two stay in sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the inline ProgressiveBlur overlay from GestureMenu along with the gestureMenuBlur z-index token and the GESTURE_MENU_* height constants added in ceb318e. The z-index approach cannot lift the trace above the blur because the trace is trapped in react-native-web View's z-index:0 stacking context. The blur will be reintroduced as a content overlay in AppComponent (Option E) so it sits between the content and the trace. See docs/superpowers/specs/2026-06-26-gesture-trace-above-blur-root-cause-and-decision.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the generic ProgressiveBlur as a sibling of <Content/> in AppComponent, driven by gestureStore, so it lands inside MultiGesture's <View> between the content and the trace. Content is position:relative; z-index:content, so it paints in the positive-z-index phase — above a z-index:auto overlay. Add a gestureContentBlur z-index token (between content and gestureTrace) and apply it via a wrapper so the blur paints above the content but below the trace: content (2) -> gestureContentBlur (23) -> gestureTrace (24), all within View's stacking context. The content behind the menu blurs while the trace stays sharp. See docs/superpowers/specs/2026-06-26-gesture-trace-above-blur-root-cause-and-decision.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ormula (cybersemics#3791) Derive the gesture-menu blur height from commands.length and shared GESTURE_MENU_* rem constants, publishing it to gestureStore; AppComponents content blur consumes it and caps a top-anchored wrapper. Constants must track GestureMenuItem row styling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bff5266 to
245efba
Compare
|
|
|
First off, my visual review – looks great! Exactly what I was after. 🙏 My concern is architectural. Now that the blur lives outside the menu, it has to calculate the menu's height itself ( Here's what I'm thinking... the blur used to size itself for free – I know the catch is untangling
And it delivers on the flexibility I'd asked for there too – being able to change the layer order later without surgery each time:
What do you think? It might be worth a chat with AI to see how feasible this is. If this feels right, I'm happy to have a think with you about how to structure the refactor with you if that's useful. If there's a reason we shouldn't take this route please also lmk! |
|
@fbmcipher btw, after looking at #3801, I just realized that the GestureMenu background supposed to be full-screen height. However, I only able to find one design in Figma looks like this, could you confirm the Design 2 below is the final design?
I also had a chat with Claude, and one concern about separating And after spending more time understanding the The only downside I see is that it gives If we go with that reparenting GestureMenu approach, my plan would be to structure it like this. Based on Claude observations, this seems feasible and shouldn't introduce any regressions compared to what I've implemented so far. I think the re-parenting approach is a better fit than refactoring |
|
@fbmcipher Apologies, I was a bit hallucinating when I created the proposed hierarchy. Now I understand why the previous hierarchy was hard to accept, since it was still separating the blur from the In the corrected proposed hierarchy,
With this proposed hierarchy, we no longer need to split the blur and And thanks for the suggestions on how we could separate But I'd like to hear your thoughts on the corrected hierarchy proposal. Do you think it still makes sense? If not, I can go with the common ancestor approach instead. By the way, regarding the blur height resizing, could you respond to my question from my earlier post? Asking this because if we decide not to make the blur height dynamic for the final design, I can remove that requirement from this PR implementation as well. The other issue mentions that we're planning to move away from the fixed full-height behavior.
|
|
@cindmichelle Hey, no worries about the hallucination, it's easily done. Just be sure to rigorously test your agent's assumptions. I've noticed especially with issues of architecture/composition, agents can still really struggle – even high-powered ones like Opus/Fable. LLMs are often falsely confident and we need to verify their results by asking them lots of questions, challenging their assumptions with our knowledge, and reasoning through its responses for ourselves.
To be clear, in your original post you said that Of the two architectures, I honestly think either is fine as long as it meets our requirements. I was at first a little unsure about moving the entire GestureMenu into MultiGesture, but I see now that the As long as we don't have to manually calculate the blur's height, and the layer order can be flexible, I'm happy with either. Sorry, missed this in my last reply! We probably want to keep the Gesture Menu full height, since we changed the design in #3801. |





Fixes #3791
Summary
When performing a gesture, the TraceGesture (drawn stroke) appeared blurry over the gesture menu commands instead of staying sharp.
The root cause is that the gesturemenu's blur and TraceGesture lived in different stacking contexts, so z-index couldn't order them. react-native-web's creates its own stacking context (position: relative; z-index: 0), trapping TraceGesture inside it; the blur lived at root level, and since View is z-index: 0 in root, the entire View (trace included) always painted below the blur.
The fix works in two steps, since CSS stacking requires both to be true before it can order two elements.
GestureContentBluras a sibling of in AppComponent (it lands inside View via the children slot).gestureContentBlur: 23z-index token — orders it correctly between content (2) and gestureTrace (24).Changes
GestureContentBlur.tsx— replaces the gesture menu's dedicated blur with the sharedProgressiveBlurcomponent, so there's one blur implementation instead of two to maintain, and we inherit its Safaribackdrop-filterworkarounds. Sizes the blur height dynamically to the number of commands shown so it covers the menu footprint. Renders inside<View>viaAppComponent's children slot.constants.ts— pulls the gesture menu's sizing values (row height, spacing, padding) into one shared file, so bothGestureMenu(which draws the menu) andGestureContentBlur(which calculates the blur height from them) use the same numbers.AppComponent.tsx— mounts<GestureContentBlur />as sibling of<Content/>on touch devicesMultiGesture.tsx— setzIndex: 'auto'on<View>to dissolve its stacking contextpanda.config.ts— addsgestureContentBlur: 23token betweencontent (2)andgestureTrace (24)GestureMenu.tsx/GestureMenuItem.tsx— moved undersrc/components/GestureMenu/; removed inlineProgressiveBlurNotes
Dynamic blur height — rather than measuring the rendered menu with a
ResizeObserver, the blur works out its own height with a formula:Every value in that formula comes from the shared
constants.ts— the same numbersGestureMenuuses to lay itself out. This keeps the blur in sync with the menu: when a command is added or removed, the menu and the blur both grow by one row's worth of height, with no measuring and no extra render.Two things to note. The header value (
GESTURE_MENU_HEADER_REM) is a best-effort estimate of the header area (label, divider, margin) rather than a real measurement. And because a selected command row is taller than a normal one (it shows an extra description), the formula underestimates the height when a row is expanded — but the gap stays small in practice, since thebottomTailpadding already extends the blur beyond the command rows and absorbs most of the difference.no longer forms a stacking context — setting zIndex: 'auto' on MultiGesture's was required so the blur and trace could be ordered against each other. This change came out of testing on Capacitor iOS and Safari, where the NavBar was appearing above the blur because of this same stacking context — which is what led us to this solution. Side effect: View's children are now ordered directly in the root stacking context instead of being isolated at z-index: 0, so any future z-index added inside View now competes at root level. Regression-checked the affected children — TraceGesture, ScrollZone, and Content still layer and render correctly, and ProgressiveBlur's existing consumers (Tip, Sidebar) are unaffected — with no visual change to any of them.
this PR is ready for visual review. The commit history looks a bit messy because this PR is branched off #4205, and since #4205 is based on a user-remote, I wasn't able to set up a proper stacked PR. It should still be fine to visually review this in parallel until #4205 is merged. This PR changes started from commit 06560eb and so on
Screenshots and Videos
Trace now above the blur
Ensure no regression against it's original ux
ios.safari.MP4
ios.capacitor.MP4
Android.cap.mp4
🤖 Generated with Claude Code