Fix horizontal grid jumping caused by scrollIntoView bubbling
scrollIntoView() in AgentPane was scrolling all ancestor containers including ProjectGrid (overflow-x: auto), causing the entire project grid to jump horizontally every time any agent produced output. Replaced with direct scrollTop/scrollTo manipulation that only affects the intended scroll container. Also removed scroll-snap-type which caused additional snap recalculation on layout changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bb09b3c0ff
commit
e8555625ff
3 changed files with 19 additions and 9 deletions
|
|
@ -286,7 +286,10 @@
|
||||||
);
|
);
|
||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
autoScroll = false;
|
autoScroll = false;
|
||||||
scrollContainer.querySelector('#msg-' + CSS.escape(msg.id))?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
const el = scrollContainer.querySelector('#msg-' + CSS.escape(msg.id));
|
||||||
|
if (el instanceof HTMLElement) {
|
||||||
|
scrollContainer.scrollTop = el.offsetTop - scrollContainer.clientHeight / 2 + el.offsetHeight / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll anchoring: two-phase pattern
|
// Scroll anchoring: two-phase pattern
|
||||||
|
|
@ -300,7 +303,7 @@
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (session?.messages.length !== undefined && wasNearBottom && autoScroll) {
|
if (session?.messages.length !== undefined && wasNearBottom && autoScroll) {
|
||||||
scrollContainer?.querySelector('#message-end')?.scrollIntoView({ behavior: 'instant' as ScrollBehavior });
|
if (scrollContainer) scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -585,7 +588,7 @@
|
||||||
<span class="burn-rate" title="Current session burn rate">${burnRatePerHr.toFixed(2)}/hr</span>
|
<span class="burn-rate" title="Current session burn rate">${burnRatePerHr.toFixed(2)}/hr</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !autoScroll}
|
{#if !autoScroll}
|
||||||
<button class="scroll-btn" onclick={() => { autoScroll = true; scrollContainer?.querySelector('#message-end')?.scrollIntoView({ behavior: 'instant' as ScrollBehavior }); }}>↓ Bottom</button>
|
<button class="scroll-btn" onclick={() => { autoScroll = true; if (scrollContainer) scrollContainer.scrollTop = scrollContainer.scrollHeight; }}>↓ Bottom</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="stop-btn" data-testid="agent-stop" onclick={handleStop}>Stop</button>
|
<button class="stop-btn" data-testid="agent-stop" onclick={handleStop}>Stop</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -607,7 +610,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<UsageMeter inputTokens={session.inputTokens} outputTokens={session.outputTokens} contextLimit={DEFAULT_CONTEXT_LIMIT} />
|
<UsageMeter inputTokens={session.inputTokens} outputTokens={session.outputTokens} contextLimit={DEFAULT_CONTEXT_LIMIT} />
|
||||||
{#if !autoScroll}
|
{#if !autoScroll}
|
||||||
<button class="scroll-btn" onclick={() => { autoScroll = true; scrollContainer?.querySelector('#message-end')?.scrollIntoView({ behavior: 'instant' as ScrollBehavior }); }}>↓ Bottom</button>
|
<button class="scroll-btn" onclick={() => { autoScroll = true; if (scrollContainer) scrollContainer.scrollTop = scrollContainer.scrollHeight; }}>↓ Bottom</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if session.status === 'error'}
|
{:else if session.status === 'error'}
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto auto 1fr auto;
|
grid-template-rows: auto auto 1fr auto;
|
||||||
min-width: 30rem;
|
min-width: 30rem;
|
||||||
scroll-snap-align: start;
|
/* scroll-snap-align removed: see ProjectGrid */
|
||||||
background: var(--ctp-base);
|
background: var(--ctp-base);
|
||||||
border: 1px solid var(--ctp-surface0);
|
border: 1px solid var(--ctp-surface0);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,21 @@
|
||||||
let slotEls = $state<Record<string, HTMLElement>>({});
|
let slotEls = $state<Record<string, HTMLElement>>({});
|
||||||
|
|
||||||
// Auto-scroll to active project only when activeProjectId changes
|
// Auto-scroll to active project only when activeProjectId changes
|
||||||
// (untrack slotEls so agent re-renders don't trigger unwanted scrolls)
|
// Uses direct scrollLeft instead of scrollIntoView to avoid bubbling to parent containers
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const id = activeProjectId;
|
const id = activeProjectId;
|
||||||
if (!id) return;
|
if (!id || !containerEl) return;
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
const el = slotEls[id];
|
const el = slotEls[id];
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
// Only scroll if the slot is not already visible
|
||||||
|
const cRect = containerEl!.getBoundingClientRect();
|
||||||
|
const eRect = el.getBoundingClientRect();
|
||||||
|
if (eRect.left >= cRect.left && eRect.right <= cRect.right) return;
|
||||||
|
containerEl!.scrollTo({
|
||||||
|
left: el.offsetLeft - containerEl!.offsetLeft,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -75,7 +82,7 @@
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
scroll-snap-type: x mandatory;
|
/* scroll-snap disabled: was causing horizontal jumps when agents auto-scroll */
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue