diff --git a/src/components/chat/ChatView.tsx b/src/components/chat/ChatView.tsx index 8be4111..c9a942d 100644 --- a/src/components/chat/ChatView.tsx +++ b/src/components/chat/ChatView.tsx @@ -31,6 +31,7 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal const [messages, setMessages] = useState(initialMessages); const [hasMore, setHasMore] = useState(initialHasMore); const [loadingMore, setLoadingMore] = useState(false); + const loadingMoreRef = useRef(false); const [streamingContent, setStreamingContent] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const [toolUses, setToolUses] = useState([]); @@ -113,7 +114,9 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal }, [initialHasMore]); const loadEarlierMessages = useCallback(async () => { - if (loadingMore || !hasMore || messages.length === 0) return; + // Use ref as atomic lock to prevent double-fetch from rapid clicks + if (loadingMoreRef.current || !hasMore || messages.length === 0) return; + loadingMoreRef.current = true; setLoadingMore(true); try { // Use _rowid of the earliest message as cursor @@ -128,9 +131,10 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal setMessages(prev => [...data.messages, ...prev]); } } finally { + loadingMoreRef.current = false; setLoadingMore(false); } - }, [sessionId, messages, hasMore, loadingMore]); + }, [sessionId, messages, hasMore]); const stopStreaming = useCallback(() => { abortControllerRef.current?.abort(); diff --git a/src/lib/platform.ts b/src/lib/platform.ts index 44796a0..0be9860 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -95,14 +95,16 @@ export function getExpandedPath(): string { return parts.join(path.delimiter); } -// TTL cache for findClaudeBinary to avoid repeated filesystem probes +// TTL cache for findClaudeBinary to avoid repeated filesystem probes. +// Only caches "found" results; "not found" is never cached so a fresh +// install is detected immediately on the next check. let _cachedBinaryPath: string | undefined | null = null; // null = not cached let _cachedBinaryTimestamp = 0; const BINARY_CACHE_TTL = 60_000; // 60 seconds /** * Find and validate the Claude CLI binary. - * Results are cached for 60s to avoid redundant filesystem probes on every poll. + * Positive results are cached for 60s; negative results are never cached. */ export function findClaudeBinary(): string | undefined { const now = Date.now(); @@ -111,8 +113,13 @@ export function findClaudeBinary(): string | undefined { } const found = _findClaudeBinaryUncached(); - _cachedBinaryPath = found; - _cachedBinaryTimestamp = now; + if (found) { + _cachedBinaryPath = found; + _cachedBinaryTimestamp = now; + } else { + // Don't cache "not found" — user may install CLI any moment + _cachedBinaryPath = null; + } return found; }