From 18232d891175a135dcf58e0e398ae13c6a53cb1b Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 15 Jul 2026 20:08:15 -0700 Subject: [PATCH] [eric] dashboard: scroll-focus set on any card press + non-card scrollables (menus/dropdowns) scroll natively; fresh chat scrollable on spawn --- .../pages/Dashboard/hooks/interaction/useCanvasControls.ts | 4 ++-- .../Dashboard/hooks/interaction/useDashboardInteractions.ts | 6 +++--- .../app/pages/Dashboard/hooks/state/useDashboardUiState.ts | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/useCanvasControls.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/useCanvasControls.ts index 9c10795d..91cc1817 100644 --- a/frontend/src/app/pages/Dashboard/hooks/interaction/useCanvasControls.ts +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/useCanvasControls.ts @@ -265,10 +265,10 @@ export function useCanvasControls(zoomSensitivity: number = 50, contentBounds?: } if (cls === 'scrollable' && !isModifierWheel) { - // Google Maps model: plain scroll zooms the canvas over ANY card (chat, app, scheduled task) UNLESS you've clicked INTO that card to read it. So a scrollable child only eats the wheel when its card is the scroll-focused one; otherwise fall through to canvas zoom. + // Google Maps model: plain scroll zooms the canvas over a CARD (chat, scheduled task) UNLESS you've clicked INTO it. Only a card that isn't scroll-focused diverts to zoom; non-card scrollable UI (dropdowns, menus, nested panels) always scrolls natively, and a focused card scrolls its content. const cardEl = target.closest('[data-select-id]'); const cardId = cardEl?.getAttribute('data-select-id') ?? null; - if (!cardId || cardId !== getScrollFocusedCard()) { + if (cardId && cardId !== getScrollFocusedCard()) { target = target.parentElement; continue; } diff --git a/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardInteractions.ts b/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardInteractions.ts index b4126d03..ba88f8f1 100644 --- a/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardInteractions.ts +++ b/frontend/src/app/pages/Dashboard/hooks/interaction/useDashboardInteractions.ts @@ -53,8 +53,6 @@ export function useDashboardInteractions({ selection.selectCard(id, type, false); dispatch(bringToFront({ id, type })); - // Clicking INTO a card focuses it for scrolling: plain wheel now reads its content instead of zooming the canvas (Google Maps model). Clicking blank canvas clears it (below). - setScrollFocusedCard(id); // The Workflows window is an app you click around inside, not a card you re-center every tap. Single-click only raises + selects it; double-click still zoom-to-fits (handleCardDoubleClick). Without this, clicking any button inside it yanked the canvas into a re-zoom. if (type === 'workflows-hub' || type === 'workflows-monitor') return; @@ -93,6 +91,8 @@ export function useDashboardInteractions({ const handleBringToFront = useCallback((id: string, type: CardType) => { dispatch(bringToFront({ id, type })); + // Pressing ANY part of a card (header, body, composer) focuses it for scrolling, so its content scrolls instead of the canvas zooming (Google Maps model). Fires via onPointerDownCapture on every card, so a click into a chat's composer focuses it even though the body swallows the bubble. Cleared on blank-canvas press. + setScrollFocusedCard(id); }, [dispatch]); // A click INSIDE a webview's page never reaches the host DOM; BrowserCard forwards the guest's app-clicked IPC as this event. Select + raise only, no camera fit: you're clicking around inside the page, re-framing the canvas every tap would be hostile (same carve-out as the Workflows window). @@ -112,7 +112,7 @@ export function useDashboardInteractions({ if (agentDriven) return; selection.selectCard(browserId, 'browser', false); dispatch(bringToFront({ id: browserId, type: 'browser' })); - // Clicking inside a browser's page focuses it: plain wheel now scrolls the page instead of zooming the canvas. + // In-guest clicks never reach the host capture handler, so mark the browser focused here, mainly to UN-focus any chat so scroll over other cards behaves right (the browser's own page scroll/zoom is native regardless). setScrollFocusedCard(browserId); }; window.addEventListener('openswarm:browser-guest-select', onGuestSelect); diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardUiState.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardUiState.ts index 95859a80..c815def2 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardUiState.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardUiState.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import type { CardPosition } from '@/shared/state/dashboardLayoutSlice'; +import { setScrollFocusedCard } from '@/shared/cardScrollFocus'; import type { useDashboardSelection } from './useDashboardSelection'; type Selection = ReturnType; @@ -45,6 +46,8 @@ export function useDashboardUiState(selection: Selection, cards: Record>({});