diff --git a/FEEDBACK.md b/FEEDBACK.md
index 4864a0a..afc6534 100644
--- a/FEEDBACK.md
+++ b/FEEDBACK.md
@@ -671,3 +671,27 @@
- **Status:** done
- **Completed:** 2026-02-13 — commit `2f25c45`
- **Description:** Textarea has an ugly/thick accent-colored border (cyan) visible in the screenshot. The border around the chat input textarea looks bad — it should be more subtle (thin border, muted color, or no visible border at all). The input area should blend cleanly with its container, not have a glowing cyan outline.
+
+## Item #63
+- **Date:** 2026-02-13
+- **Priority:** high
+- **Status:** pending
+- **Description:** "New message" indicator shows when scrolling up even when there are no new messages. The indicator should only appear when an actual new message arrives while scrolled up.
+
+## Item #64
+- **Date:** 2026-02-13
+- **Priority:** high
+- **Status:** pending
+- **Description:** Cursor desync in textarea — the cursor position gets ahead of where characters actually appear (whitespace gap between cursor and text). Likely related to HighlightedTextarea overlay sync issue.
+
+## Item #65
+- **Date:** 2026-02-13
+- **Priority:** high
+- **Status:** pending
+- **Description:** "New message" indicator and expand/collapse toggle overlap with the textarea when it grows (multi-line input). These elements should stay outside/above the textarea area and not overlap.
+
+## Item #66
+- **Date:** 2026-02-13
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Avatar image shows as broken for some deployments. Bardak's instance (deployed by Pelouse) shows a broken image. Works on Nicolas's instance. Likely the avatar URL configured by Pelouse is invalid or blocked. PinchChat should handle broken avatar images gracefully (fallback to initials or default icon).
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
index db63393..7e76ec5 100644
--- a/src/components/Chat.tsx
+++ b/src/components/Chat.tsx
@@ -75,6 +75,8 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
const isNearBottomRef = useRef(true);
const userSentRef = useRef(false);
const [showScrollBtn, setShowScrollBtn] = useState(false);
+ const [hasNewMessages, setHasNewMessages] = useState(false);
+ const prevMessageCountRef = useRef(messages.length);
const checkIfNearBottom = useCallback(() => {
const el = scrollContainerRef.current;
@@ -82,6 +84,9 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
isNearBottomRef.current = distanceFromBottom <= SCROLL_THRESHOLD;
setShowScrollBtn(distanceFromBottom > SCROLL_THRESHOLD * 2);
+ if (distanceFromBottom <= SCROLL_THRESHOLD) {
+ setHasNewMessages(false);
+ }
}, []);
const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {
@@ -97,19 +102,53 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
return () => el.removeEventListener('scroll', handler);
}, [checkIfNearBottom]);
- // Auto-scroll when messages change, but only if user is near bottom or just sent a message
+ // Reset state on session switch
+ const prevSessionKeyRef = useRef(sessionKey);
useEffect(() => {
+ if (sessionKey !== prevSessionKeyRef.current) {
+ prevSessionKeyRef.current = sessionKey;
+ prevMessageCountRef.current = messages.length;
+ setHasNewMessages(false);
+ isNearBottomRef.current = true;
+ // Scroll to bottom on session switch
+ requestAnimationFrame(() => scrollToBottom('instant'));
+ }
+ }, [sessionKey, messages.length, scrollToBottom]);
+
+ // Auto-scroll when messages change, but only if user is near bottom or just sent a message
+ const wasLoadingHistoryRef = useRef(isLoadingHistory);
+ useEffect(() => {
+ const newCount = messages.length;
+ const hadNew = newCount > prevMessageCountRef.current;
+ // Detect history load completion (don't treat as "new messages")
+ const justFinishedLoading = wasLoadingHistoryRef.current && !isLoadingHistory;
+ wasLoadingHistoryRef.current = isLoadingHistory;
+ prevMessageCountRef.current = newCount;
+
+ if (justFinishedLoading) {
+ // History just loaded — scroll to bottom, don't show indicator
+ scrollToBottom('instant');
+ isNearBottomRef.current = true;
+ setHasNewMessages(false);
+ return;
+ }
+
if (userSentRef.current) {
// User just sent a message — always scroll to bottom
userSentRef.current = false;
scrollToBottom('smooth');
isNearBottomRef.current = true;
+ setHasNewMessages(false);
return;
}
if (isNearBottomRef.current) {
scrollToBottom('smooth');
+ setHasNewMessages(false);
+ } else if (hadNew) {
+ // New message arrived while scrolled up
+ setHasNewMessages(true);
}
- }, [messages, isGenerating, scrollToBottom]);
+ }, [messages, isGenerating, isLoadingHistory, scrollToBottom]);
// Wrap onSend to flag that user initiated a message
const handleSend = useCallback((text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => {
@@ -231,28 +270,32 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
{showTyping &&