[pierre] feat: browser spawned from agent will always spawn to the right of parent chat

This commit is contained in:
SirKentut
2026-07-07 18:47:12 -07:00
parent c4789ebdab
commit d1713f901c
2 changed files with 15 additions and 1 deletions
@@ -419,6 +419,7 @@ export function placeBesideCard(
exclude?: CardPlacementExclusion,
gap: number = GRID_GAP * 12,
exact: boolean = false,
overlap: boolean = false,
): { x: number; y: number } {
const rects = collectOccupiedRects(state, expandedSessionIds, exclude);
const targetX = anchor.x + anchor.width + gap;
@@ -434,6 +435,8 @@ export function placeBesideCard(
? Math.max(...columnCards.map((c) => c.y + c.height)) + GRID_GAP
: anchor.y;
// overlap: dock beside the anchor no matter what else is there (the new card sits on top via its higher zOrder). A chat-spawned browser must land next to its chat even onto an occupied spot; dodging to a free grid cell is the "spawned off to the side" behavior we're removing. Still stacks under this chat's own browser column (targetY) so sibling browsers don't cover each other.
if (overlap) return { x: targetX, y: targetY };
// exact keeps the precise gap (so the card mirrors however its anchor was placed, e.g. a run browser matching the hub->monitor gap); grid-snapping would knock that gap off. Fall back to the snapped search only if the exact spot is taken.
if (exact && !rects.some((r) => rectsOverlap({ x: targetX, y: targetY, w: newW, h: newH }, r))) {
return { x: targetX, y: targetY };
@@ -1652,6 +1655,17 @@ const dashboardLayoutSlice = createSlice({
for (const entry of Object.values(state.glowingBrowserCards)) {
if (entry.sourceId === draftId) entry.sourceId = session.id;
}
// First-turn browser race: a browser the first message spawns carries parent_session_id = the real id, so its browser_card_added can land BEFORE this re-key, find no parent card, and fall back to the grid. Now that the chat card exists under the real id, dock each such browser beside it (freshly spawned, so not user-moved yet) and restore the tether the racing path skipped.
const parentCard = state.cards[session.id];
if (parentCard) {
for (const bc of Object.values(state.browserCards)) {
if (bc.spawned_by !== session.id) continue;
const pos = placeBesideCard(state, parentCard, bc.width, bc.height, undefined, { type: 'browser' as const, id: bc.browser_id }, undefined, false, true);
bc.x = pos.x;
bc.y = pos.y;
state.glowingBrowserCards[bc.browser_id] = { sourceId: session.id, fading: false, label: 'Use Browser' };
}
}
});
},
});
+1 -1
View File
@@ -791,7 +791,7 @@ class WebSocketManager {
let pos: { x: number; y: number } | null = null;
let glowLabel = 'Use Browser';
if (parentCard) {
pos = placeBesideCard(layoutState, parentCard, browserCard.width, browserCard.height, undefined, exclude);
pos = placeBesideCard(layoutState, parentCard, browserCard.width, browserCard.height, undefined, exclude, undefined, false, true);
} else if (sess?.workflow_run_id && layoutState.workflowsMonitorCard) {
pos = placeBesideCard(layoutState, layoutState.workflowsMonitorCard, browserCard.width, browserCard.height, undefined, exclude, WORKFLOW_CARD_GAP, true);
} else if (sess?.workflow_edit_id && layoutState.workflowsHub) {