mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-10 19:57:44 +02:00
[eric] chat: fullscreen side gutters scroll the transcript instead of eating the wheel
(cherry picked from commit fe1ebaa5 on eric/feature-requests) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PNA9xhjw5SkfuB2tJ5KwCj
This commit is contained in:
@@ -84,6 +84,7 @@ import { shallowEqual } from 'react-redux';
|
||||
import { useClaudeTokens, useThemeMode } from '@/shared/styles/ThemeContext';
|
||||
import { parseMcpToolName, getMcpInputSummary } from '@/shared/mcpToolMeta';
|
||||
import { isNarration } from './parsing/isNarration';
|
||||
import { shouldForwardGutterWheel } from './gutterWheel';
|
||||
import { openMarketplace } from '@/app/pages/Directory/openMarketplace';
|
||||
|
||||
const CONTEXT_WINDOWS: Record<string, number> = {
|
||||
@@ -831,6 +832,23 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Fullscreen centers the column inside wide empty gutters, and the gutter is where the cursor naturally rests; a vertical wheel there can only mean "scroll the thread". Only the BARE gutter forwards (target === currentTarget), so nothing inside the column can be double-scrolled, honoring the one-owner rule.
|
||||
const onGutterWheel = useCallback((e: React.WheelEvent<HTMLDivElement>) => {
|
||||
if (!shouldForwardGutterWheel({
|
||||
ctrlKey: e.ctrlKey, metaKey: e.metaKey, deltaX: e.deltaX, deltaY: e.deltaY,
|
||||
targetIsGutter: e.target === e.currentTarget,
|
||||
})) return;
|
||||
const el = scrollContainerRef.current;
|
||||
if (!el) return;
|
||||
if (e.deltaY < 0) {
|
||||
// Mirror the container's own wheel: reading up from the gutter is real follow-away intent.
|
||||
userScrollIntentUntilRef.current = performance.now() + USER_SCROLL_INTENT_MS;
|
||||
pinAbortRef.current = true;
|
||||
}
|
||||
el.scrollTop += e.deltaY;
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const scrollToBottomRafRef = useRef<number | null>(null);
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const el = scrollContainerRef.current;
|
||||
@@ -1638,7 +1656,10 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
const statusStyle = STATUS_STYLES[session.status] || { color: c.text.tertiary, bg: c.bg.secondary };
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: '100%', ...(fullscreenWash && { background: fullscreenWash }) }}>
|
||||
<Box
|
||||
onWheel={fullscreenChat ? onGutterWheel : undefined}
|
||||
sx={{ display: 'flex', height: '100%', ...(fullscreenWash && { background: fullscreenWash }) }}
|
||||
>
|
||||
<ContextDrawer />
|
||||
{/* Marks transcript AND composer as one chat, so Cmd+A from the composer can still find the conversation (ENG-231). */}
|
||||
<Box data-chat-root sx={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0, overflow: 'hidden', ...(fullscreenChat && { maxWidth: FULLSCREEN_READING_MAX_W, width: '100%', mx: 'auto' }) }}>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Run: npm test (frontend/scripts/run-tests.mjs)
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { shouldForwardGutterWheel } from './gutterWheel.ts';
|
||||
|
||||
const base = { ctrlKey: false, metaKey: false, deltaX: 0, deltaY: 40, targetIsGutter: true };
|
||||
|
||||
test('a plain vertical wheel on the bare gutter forwards', () => {
|
||||
assert.equal(shouldForwardGutterWheel(base), true);
|
||||
assert.equal(shouldForwardGutterWheel({ ...base, deltaY: -40 }), true);
|
||||
});
|
||||
|
||||
test('anything inside the column keeps its own scroll (no double-scroll)', () => {
|
||||
assert.equal(shouldForwardGutterWheel({ ...base, targetIsGutter: false }), false);
|
||||
});
|
||||
|
||||
test('pinch-zoom stays free on every surface', () => {
|
||||
assert.equal(shouldForwardGutterWheel({ ...base, ctrlKey: true }), false);
|
||||
assert.equal(shouldForwardGutterWheel({ ...base, metaKey: true }), false);
|
||||
});
|
||||
|
||||
test('horizontal-dominant gestures pass through to the canvas', () => {
|
||||
assert.equal(shouldForwardGutterWheel({ ...base, deltaX: 80, deltaY: 10 }), false);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
// Haik: in fullscreen, scrolling only works over the center text column; the wide side gutters eat
|
||||
// the wheel. A wheel on the BARE gutter forwards to the transcript; everything else keeps its owner.
|
||||
export function shouldForwardGutterWheel(e: {
|
||||
ctrlKey: boolean;
|
||||
metaKey: boolean;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
targetIsGutter: boolean;
|
||||
}): boolean {
|
||||
if (e.ctrlKey || e.metaKey) return false;
|
||||
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return false;
|
||||
return e.targetIsGutter;
|
||||
}
|
||||
Reference in New Issue
Block a user