[eric] auto-focus canvas on newly created

browser cards from link clicks
This commit is contained in:
ciregenz
2026-04-09 16:59:04 -07:00
parent e252fcc20f
commit 98aa3a60f1
3 changed files with 48 additions and 1 deletions
@@ -39,6 +39,7 @@ import {
bringToFront,
setGlowingAgentCard,
clearGlowingAgentCard,
clearPendingFocusBrowserId,
DEFAULT_CARD_W,
DEFAULT_CARD_H,
EXPANDED_CARD_MIN_H,
@@ -451,6 +452,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const pendingBrowserUrl = useAppSelector((state) => state.tempState.pendingBrowserUrl);
const pendingFocusAgentId = useAppSelector((state) => state.tempState.pendingFocusAgentId);
const pendingFocusBrowserId = useAppSelector((state) => state.dashboardLayout.pendingFocusBrowserId);
useEffect(() => {
if (!dashboardId) return;
@@ -533,6 +535,38 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
}, 350);
}, [isActive, pendingFocusAgentId, layoutInitialized, dispatch, canvas.actions, handleHighlightCard]);
// Auto-focus a newly created browser card. The reducer that handles
// addBrowserCard sets pendingFocusBrowserId to the new card's id; this
// effect picks it up, pans/zooms the canvas to center on it, briefly
// highlights it, then clears the signal. Mirrors the pendingFocusAgentId
// pattern above so link clicks (intercepted in AppShell) get the same
// auto-focus behavior as the "+ Browser" toolbar button.
//
// Uses zoom=0.8 (the same value handleCardClick uses for browser cards
// at line ~344) instead of letting fitToCards auto-derive a zoom from
// padding. Browser cards are large (1280x800), so the auto-derived zoom
// would land around ~58% which feels too far back; 0.8 matches the
// "click on a browser to focus" experience the user expects.
useEffect(() => {
if (!isActive) return;
if (!pendingFocusBrowserId || !layoutInitialized) return;
const browserId = pendingFocusBrowserId;
dispatch(clearPendingFocusBrowserId());
hasFittedRef.current = true;
setTimeout(() => {
const card = store.getState().dashboardLayout.browserCards[browserId];
if (card) {
canvas.actions.fitToCards(
[{ x: card.x, y: card.y, width: card.width, height: card.height }],
1.15,
true,
0.8,
);
handleHighlightCard(browserId);
}
}, 200);
}, [isActive, pendingFocusBrowserId, layoutInitialized, dispatch, canvas.actions, handleHighlightCard]);
useEffect(() => {
if (!layoutInitialized || restoredExpandedRef.current) return;
restoredExpandedRef.current = true;
@@ -63,6 +63,11 @@ export interface DashboardLayoutState {
nextZOrder: number;
loading: boolean;
initialized: boolean;
// Transient signal: when a new browser card is created via addBrowserCard
// (link click, "+ Browser" button, pending URL flow), the reducer sets this
// to the new card's id. Dashboard.tsx watches it and pans/zooms the canvas
// to center on the new card, then dispatches clearPendingFocusBrowserId.
pendingFocusBrowserId: string | null;
}
const initialState: DashboardLayoutState = {
@@ -76,6 +81,7 @@ const initialState: DashboardLayoutState = {
nextZOrder: 1,
loading: false,
initialized: false,
pendingFocusBrowserId: null,
};
interface LayoutPayload {
@@ -403,6 +409,12 @@ const dashboardLayoutSlice = createSlice({
height: DEFAULT_BROWSER_CARD_H,
zOrder: state.nextZOrder++,
};
// Signal Dashboard.tsx to pan/zoom and highlight this new card.
state.pendingFocusBrowserId = id;
},
clearPendingFocusBrowserId(state) {
state.pendingFocusBrowserId = null;
},
addBrowserCardFromBackend(state, action: PayloadAction<BrowserCardPosition>) {
@@ -764,6 +776,7 @@ export const {
setGlowingAgentCard,
fadeGlowingAgentCard,
clearGlowingAgentCard,
clearPendingFocusBrowserId,
resetLayout,
} = dashboardLayoutSlice.actions;
File diff suppressed because one or more lines are too long