fix(embed): make frame-bounded interactions behave inside the dashboard - #449
Open
prajjwalkumar17 wants to merge 1 commit into
Open
prajjwalkumar17 wants to merge 1 commit into
prajjwalkumar17 wants to merge 1 commit into
Conversation
Three things the embedded workspace gets wrong because the frame is not the whole window: - Floating layers dismissed on a document mousedown, which never sees clicks landing on the dashboard chrome outside the frame, so menus hung open while the user worked elsewhere. Adds useCloseOnFrameBlur and wires it into the ten layers across nine components that render in embed mode. Armed only when embedded: standalone, a window blur means a tab or app switch, where staying open is the expected behaviour. - EmbedSessionRefresh reposted de:session-expired every 6s forever, so a session the dashboard cannot restore spun indefinitely. Caps the attempts and then shows a terminal card. A successful re-mint replaces the frame's document, so the cap only bites when the session is genuinely unrecoverable. - The 401/403 branch fell through to a /login navigation, which cannot work in the frame. Gated like its siblings. RedirectSessionExpired already handled the SSO case, so this only affected a non-redirect session. Also replaces the catch-all route's silent redirect to the overview with an explicit in-frame message when embedded: simulatorEnabled compiles the Decision Simulator route out of some builds while the dashboard still offers it, and a silent swap reads as the wrong screen loading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jagan-jaya
approved these changes
Sep 28, 2026
AnkitKmrGupta
approved these changes
Sep 28, 2026
This branch has not been deployed
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.
Follow-ups to #448, from an audit of what else assumes this app owns the whole window. #448 covered layout inside the frame; this covers behaviour at the frame boundary.
1. Floating layers never dismissed when you clicked the dashboard
Ten layers across nine components dismiss on
document.addEventListener('mousedown', …). That document never sees clicks landing on the dashboard chrome outside the iframe, so an open menu stayed up while the user was already working elsewhere.New hook
hooks/useCloseOnFrameBlur.ts, wired into:ui/SelectMenu.tsxui/SearchableMultiSelect.tsxui/SearchableSelect.tsxui/TableControls.tsxHeaderFilter,RowMenu)ui/Combobox.tsxui/GatewaySelect.tsxui/TimeRangeFilter.tsxpages/AnalyticsPage.tsxpages/ClusterFilterBar.tsxArmed only when embedded. Standalone, a
windowblur means the user switched tab or app — every other dropdown on the web stays open across that, so gating onisEmbedded()keeps existing behaviour byte-identical. Where a component has a realclose()(clearing a search query as well as the open flag), the hook is passed that rather than a baresetOpen(false), so blur dismissal matches the existing outside-click path exactly.TopBar,Sidebar,NotificationBellandScopeSwitcheralso use this pattern but are deliberately not wired — embed mode doesn't render them.2. Session-refresh loop had no cap
EmbedSessionRefreshrepostedde:session-expiredevery 6s forever. A session the dashboard can't restore (revoked, or the profile no longer cut over) spun indefinitely against the parent's re-mint throttle. Now capped, then a terminal card.A successful re-mint replaces the frame's document and unmounts the component, so the cap only bites when the session is genuinely unrecoverable.
3.
/logincould render inside the frameThe 401/403 branch fell through to
<Navigate to="/login" replace />, which can't work embedded. Gated like its siblings at lines 56 and 123.Scope note:
RedirectSessionExpiredalready embed-gated, and embedded users arrive via SSO withhs_ids, so that branch catches them first. This only affected a non-redirect session inside the frame — narrower than it first looks, but still a dead end.4. Unknown route silently became the Overview page
The catch-all did
<Navigate to="." replace />, dropping the user on a chrome-less Overview with the dashboard never told. This is reachable in practice:simulatorEnabledcompiles the Decision Simulator route out of some builds while the dashboard still offers that section. Embedded, it now renders an explicit "This section isn't available" panel; standalone the redirect is unchanged.Verification
tsc --noEmitexit 0,vite buildexit 0. The build emits 3 pre-existingcss-syntax-errorwarnings — I confirmed the identical count on pristinemainby stashing these changes and rebuilding, so they are not from this PR.Every edit in part 1 is additive (an import plus a hook call); no existing effect, handler or JSX was restructured.
🤖 Generated with Claude Code