fix(web-ui): give portaled overlays their own stacking layer - #2390
Merged
bobleer merged 1 commit intoAug 20, 2026
Merged
Conversation
The appearance overlay host was an unstyled div on the body, so an overlay portaled into it kept competing, in the root stacking context, with the container it had escaped from. Session UI lives inside .bitfun-app-main-workspace (a stacking context at z-index 1), so its menus cleared it at $z-dropdown. The floating mini chat panel instead sits in the root context at $z-overlay + 1, which buried every menu its composer opened: the composer's add-boost menu and slash-command picker rendered behind the panel, and the panel's full-screen backdrop took their clicks, so the "+" button read as dead. Make the host the layer it was always meant to be: one stacking context above every container that hosts app UI, and below the chrome that must outrank overlays (notifications, context menus, splash), which therefore needs no change. Overlay z-indexes now only order overlays against each other. The host box mirrors the initial containing block it used to sit in so absolutely positioned overlays keep their reference rect, and hit testing is handed back to the overlays through a zero-specificity rule so a tooltip that opts out of pointer events still wins.
bobleer
force-pushed
the
bob/bitfun-chat-bubble-plus-button-567530-explore
branch
from
August 20, 2026 08:27
a2b0fea to
0069201
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Same fix as #2389, cherry-picked onto
1.0.0-explore— the overlay host and.bitfun-fmcare byte-identical on both branches, so the bug and the fix carry over unchanged.Summary
The
+(add-boost) button in the floating chat bubble's composer did nothing when clicked. The menu was opening — it was just painted behind the bubble panel, whose full-screen backdrop then swallowed the clicks.Root cause is not in the bubble.
getAppearanceOverlayHost()returned an unstyled<div>on<body>, so an overlay portaled into it still competed, in the root stacking context, with the container it had escaped from:<main class="bitfun-app-main-workspace">, a stacking context atz-index: 1. Its composer menus clear it easily at$z-dropdown(60) — which is why the same code works there..bitfun-fmcisposition: fixed; z-index: $z-overlay + 1(101) in the root context. 60 < 101, so every menu the bubble's composer opened rendered underneath the panel, and.bitfun-fmc__backdrop(inset: 0, same subtree) took the clicks.So the host was a mount point, not a layer. This PR makes it a layer: one stacking context at a new
$z-overlay-host(300) — above every container that hosts app UI, below the chrome that must outrank overlays (notifications 400, context menus 500, splash 9999), which therefore needs no change. Overlayz-indexvalues now only order overlays against each other inside the host.Two details are load-bearing and called out in the stylesheet:
position: fixed; inset: 0), so absolutely positioned overlays keep the same reference rect, and the rule set deliberately excludestransform/filter/backdrop-filter/will-change/contain, which would re-anchor everyposition: fixedoverlay to the host.:where()rule, so an overlay that opts out of pointer events (tooltips) keeps its own declaration.Fixes the same class of bug for anything else portaled out of a high-
z-indexcontainer — the bubble's/slash-command picker (also$z-dropdown) was dead for the same reason.Type and Areas
Type: bug fix
Areas: web UI
Motivation / Impact
Users of the floating chat bubble could not reach agent modes, image attachment, Skills, or "new session" — the entire
+menu and the/command picker were unreachable. The fix is at the layer level, so future overlays are correct by construction instead of each one escalating its ownz-index(the existing9999/10000values around the codebase are artifacts of that escalation; retiring them is left as separate cleanup).Verification
Behaviour was checked in a real engine on a page reproducing the app's DOM/CSS (
elementFromPointat each overlay's rect):+triggerfmc__panel(buried)menupointer-events: none(click-through)Fixed overlays kept identical coordinates (
[120, 400]) before and after, confirming the fixed host does not re-anchor them.New unit tests in
AppearanceOverlayHost.test.tscover the host's identity, the stacking-context contract, the ordering against the neighbouring bands, the containing-block trap, and the pointer-events handoff.Reviewer Notes
The value
300is chosen so no other layer has to move: it clears.bitfun-fmc(101) and the workspace context (1) while staying under$z-notification(400) and$z-context-menu(500), which keep their current precedence over overlays.Checklist