authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 18:08:34-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-12 02:12:40-08:00
log36cdceb4bff336dbb53ad638806e4e1a88043b73
tree916328c717e57901f6dfe3cf610ad9bd1106f993
parent3bfda73dcec029957c6894dc68ffff7f78a03047

Fix ChatView height scroll


2 files changed, 32 insertions(+), 9 deletions(-)

apps/web/src/components/ChatView.tsx+3-3
......@@ -628,7 +628,7 @@ export default function ChatView() {
628628 // Empty state: no active thread
629629 if (!activeThread) {
630630 return (
631 <div className="flex flex-1 flex-col bg-background text-muted-foreground/40">
631 <div className="flex min-h-0 flex-1 flex-col bg-background text-muted-foreground/40">
632632 {isElectron && <div className="drag-region h-[52px] shrink-0" />}
633633 <div className="flex flex-1 items-center justify-center">
634634 <div className="text-center">
......@@ -640,7 +640,7 @@ export default function ChatView() {
640640 }
641641
642642 return (
643 <div className="flex flex-1 flex-col bg-background">
643 <div className="flex min-h-0 flex-1 flex-col bg-background">
644644 {/* Top bar */}
645645 <header
646646 className={`flex items-center justify-between border-b border-border px-5 pb-3 ${isElectron ? "drag-region pt-[28px]" : "pt-3"}`}
......@@ -787,7 +787,7 @@ export default function ChatView() {
787787 )}
788788
789789 {/* Messages */}
790 <div className="flex-1 overflow-y-auto px-5 py-4">
790 <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
791791 {activeThread.messages.length === 0 && !isWorking ? (
792792 <div className="flex h-full items-center justify-center">
793793 <p className="text-sm text-muted-foreground/30">
apps/web/src/components/ThreadTerminalDrawer.tsx+29-6
......@@ -129,6 +129,7 @@ export default function ThreadTerminalDrawer({
129129 startY: number;
130130 startHeight: number;
131131 } | null>(null);
132 const didResizeDuringDragRef = useRef(false);
132133
133134 useEffect(() => {
134135 onHeightChangeRef.current = onHeightChange;
......@@ -152,11 +153,17 @@ export default function ThreadTerminalDrawer({
152153 [],
153154 );
154155
155 const fitAndResizeTerminal = useCallback(() => {
156 const fitAndResizeTerminal = useCallback((preserveBottom = false) => {
156157 const activeTerminal = terminalRef.current;
157158 const activeFitAddon = fitAddonRef.current;
158159 if (!activeTerminal || !activeFitAddon) return;
160 const wasAtBottom =
161 preserveBottom &&
162 activeTerminal.buffer.active.viewportY >= activeTerminal.buffer.active.baseY;
159163 activeFitAddon.fit();
164 if (wasAtBottom) {
165 activeTerminal.scrollToBottom();
166 }
160167 void api.terminal
161168 .resize({
162169 threadId,
......@@ -177,13 +184,14 @@ export default function ThreadTerminalDrawer({
177184 if (event.button !== 0) return;
178185 event.preventDefault();
179186 event.currentTarget.setPointerCapture(event.pointerId);
187 didResizeDuringDragRef.current = false;
180188 resizeStateRef.current = {
181189 pointerId: event.pointerId,
182190 startY: event.clientY,
183 startHeight: drawerHeight,
191 startHeight: drawerHeightRef.current,
184192 };
185193 },
186 [drawerHeight],
194 [],
187195 );
188196
189197 const handleResizePointerMove = useCallback(
......@@ -194,6 +202,11 @@ export default function ThreadTerminalDrawer({
194202 const clampedHeight = clampDrawerHeight(
195203 resizeState.startHeight + (resizeState.startY - event.clientY),
196204 );
205 if (clampedHeight === drawerHeightRef.current) {
206 return;
207 }
208 didResizeDuringDragRef.current = true;
209 drawerHeightRef.current = clampedHeight;
197210 setDrawerHeight(clampedHeight);
198211 },
199212 [],
......@@ -207,9 +220,13 @@ export default function ThreadTerminalDrawer({
207220 if (event.currentTarget.hasPointerCapture(event.pointerId)) {
208221 event.currentTarget.releasePointerCapture(event.pointerId);
209222 }
223 if (!didResizeDuringDragRef.current) {
224 return;
225 }
210226 syncHeight(drawerHeightRef.current);
227 fitAndResizeTerminal(true);
211228 },
212 [syncHeight],
229 [fitAndResizeTerminal, syncHeight],
213230 );
214231
215232 useEffect(() => {
......@@ -219,7 +236,7 @@ export default function ThreadTerminalDrawer({
219236 if (changed) {
220237 setDrawerHeight(clampedHeight);
221238 } else {
222 fitAndResizeTerminal();
239 fitAndResizeTerminal(true);
223240 }
224241 if (!resizeStateRef.current) {
225242 syncHeight(clampedHeight);
......@@ -366,7 +383,9 @@ export default function ThreadTerminalDrawer({
366383 }
367384 });
368385
369 const fitTimer = window.setTimeout(fitAndResizeTerminal, 30);
386 const fitTimer = window.setTimeout(() => {
387 fitAndResizeTerminal(true);
388 }, 30);
370389 void openTerminal();
371390
372391 return () => {
......@@ -396,8 +415,12 @@ export default function ThreadTerminalDrawer({
396415 const terminal = terminalRef.current;
397416 const fitAddon = fitAddonRef.current;
398417 if (!terminal || !fitAddon) return;
418 const wasAtBottom = terminal.buffer.active.viewportY >= terminal.buffer.active.baseY;
399419 const frame = window.requestAnimationFrame(() => {
400420 fitAddon.fit();
421 if (wasAtBottom) {
422 terminal.scrollToBottom();
423 }
401424 void api.terminal
402425 .resize({
403426 threadId,