revert(dashboard): undo imperative visualViewport write in ChatView
The imperative useLayoutEffect approach made mobile worse — first tap flickered and didn't bring up the keyboard, while the original swipe-overlap symptom remained. Restoring the previous React-state flow until a better fix is identified. Removes the changeset that shipped with the failed attempt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fix ChatView composer sliding over messages on mobile when the user
|
||||
swipes with the keyboard up. The visualViewport `--vv-height` and
|
||||
`--vv-offset-top` CSS vars were being routed through React state, so
|
||||
on iOS — which fires visualViewport scroll/resize on the same frame
|
||||
as its keyboard animation — the thread translation lagged by one
|
||||
paint. The composer briefly appeared to slide over the message list
|
||||
during a pan. The vars are now written imperatively in a
|
||||
`useLayoutEffect` directly to the `.chat-thread` element on every
|
||||
visualViewport event, mirroring the working pattern in
|
||||
`QuickChatFAB.tsx:1032-1052`. Only `--keyboard-overlap` (a structural
|
||||
open/close signal) still goes through React state.
|
||||
@@ -1,6 +1,6 @@
|
||||
// ChatView.css is imported eagerly from App.tsx to avoid a flash of
|
||||
// unstyled content when the lazy chunk loads. Do not re-import here.
|
||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import type { Components } from "react-markdown";
|
||||
@@ -793,52 +793,19 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { keyboardOverlap, keyboardOpen } = useMobileKeyboard({
|
||||
const { keyboardOverlap, viewportHeight, viewportOffsetTop, keyboardOpen } = useMobileKeyboard({
|
||||
enabled: isMobile && !!activeSession,
|
||||
});
|
||||
|
||||
// Only structural per-open/close vars go through React state. The
|
||||
// high-frequency --vv-height / --vv-offset-top vars are written
|
||||
// imperatively below to avoid a one-frame lag during iOS keyboard
|
||||
// pan that causes the composer to slide over the messages list.
|
||||
const threadKeyboardStyle: CSSProperties =
|
||||
keyboardOpen
|
||||
? ({ "--keyboard-overlap": `${keyboardOverlap}px` } as CSSProperties)
|
||||
? ({
|
||||
"--keyboard-overlap": `${keyboardOverlap}px`,
|
||||
"--vv-offset-top": `${viewportOffsetTop}px`,
|
||||
...(viewportHeight !== null ? { "--vv-height": `${viewportHeight}px` } : {}),
|
||||
} as CSSProperties)
|
||||
: {};
|
||||
|
||||
const threadRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Mirror visualViewport metrics onto the .chat-thread element as CSS
|
||||
// vars synchronously, bypassing React state. iOS fires
|
||||
// visualViewport scroll/resize events on the same frame as its own
|
||||
// keyboard / pan animation; deferring writes through React state
|
||||
// makes the thread (and its bottom-pinned composer) lag by one paint
|
||||
// — visible as the composer momentarily sliding over messages while
|
||||
// the user pans. Mirrors QuickChatFAB.tsx:1032-1052 which uses the
|
||||
// same approach and works correctly on mobile.
|
||||
useLayoutEffect(() => {
|
||||
if (!keyboardOpen) return;
|
||||
if (typeof window === "undefined" || !window.visualViewport) return;
|
||||
const thread = threadRef.current;
|
||||
if (!thread) return;
|
||||
|
||||
const vv = window.visualViewport;
|
||||
const apply = () => {
|
||||
thread.style.setProperty("--vv-height", `${vv.height}px`);
|
||||
thread.style.setProperty("--vv-offset-top", `${vv.offsetTop || 0}px`);
|
||||
};
|
||||
|
||||
apply();
|
||||
vv.addEventListener("resize", apply);
|
||||
vv.addEventListener("scroll", apply);
|
||||
return () => {
|
||||
vv.removeEventListener("resize", apply);
|
||||
vv.removeEventListener("scroll", apply);
|
||||
thread.style.removeProperty("--vv-height");
|
||||
thread.style.removeProperty("--vv-offset-top");
|
||||
};
|
||||
}, [keyboardOpen]);
|
||||
|
||||
const filteredSkills = useMemo(() => {
|
||||
const normalizedFilter = skillFilter.trim().toLowerCase();
|
||||
const matchingSkills = normalizedFilter
|
||||
@@ -1737,7 +1704,7 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
)}
|
||||
|
||||
{/* Thread */}
|
||||
<div className="chat-thread" ref={threadRef} style={threadKeyboardStyle}>
|
||||
<div className="chat-thread" style={threadKeyboardStyle}>
|
||||
{/* Header - always rendered in desktop/tablet, only rendered in mobile when viewing a thread */}
|
||||
{(hasThreadInView || !isMobile) && (
|
||||
<div className="chat-thread-header">
|
||||
|
||||
Reference in New Issue
Block a user