Skip to content

fix(embed): make frame-bounded interactions behave inside the dashboard - #449

Open
prajjwalkumar17 wants to merge 1 commit into
mainfrom
fix/de-embed-frame-interactions
Open

prajjwalkumar17 wants to merge 1 commit into
mainfrom
fix/de-embed-frame-interactions

Conversation

@prajjwalkumar17

Copy link
Copy Markdown
Member

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:

file layers
ui/SelectMenu.tsx 1
ui/SearchableMultiSelect.tsx 1
ui/SearchableSelect.tsx 1
ui/TableControls.tsx 2 (HeaderFilter, RowMenu)
ui/Combobox.tsx 1
ui/GatewaySelect.tsx 1
ui/TimeRangeFilter.tsx 1
pages/AnalyticsPage.tsx 1
pages/ClusterFilterBar.tsx 1

Armed only when embedded. Standalone, a window blur means the user switched tab or app — every other dropdown on the web stays open across that, so gating on isEmbedded() keeps existing behaviour byte-identical. Where a component has a real close() (clearing a search query as well as the open flag), the hook is passed that rather than a bare setOpen(false), so blur dismissal matches the existing outside-click path exactly.

TopBar, Sidebar, NotificationBell and ScopeSwitcher also use this pattern but are deliberately not wired — embed mode doesn't render them.

2. Session-refresh loop had no cap

EmbedSessionRefresh reposted de:session-expired every 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. /login could render inside the frame

The 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: RedirectSessionExpired already embed-gated, and embedded users arrive via SSO with hs_ 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: simulatorEnabled compiles 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 --noEmit exit 0, vite build exit 0. The build emits 3 pre-existing css-syntax-error warnings — I confirmed the identical count on pristine main by 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

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>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants