Fix/active session tracking (#176)

* refactor(chat): enhance scroll management

Add bottom detection with epsilon tolerance for precise positioning
Introduce pending anchor restoration for seamless session switching
Include active turn anchor and spacer height in message store
This commit is contained in:
Bohdan Triapitsyn
2026-01-19 02:48:56 +02:00
committed by GitHub
parent 3df362b220
commit 28d9324735
6 changed files with 217 additions and 295 deletions
@@ -167,18 +167,24 @@ export const ChatContainer: React.FC = () => {
try {
await loadMessages(currentSessionId);
} finally {
if (typeof window === 'undefined') {
scrollToBottom();
} else {
window.requestAnimationFrame(() => {
const currentPhase = sessionActivityPhase?.get(currentSessionId) ?? 'idle';
const isActivePhase = currentPhase === 'busy' || currentPhase === 'cooldown';
const shouldSkipScroll = isActivePhase && hasActiveAnchor;
if (!shouldSkipScroll) {
if (typeof window === 'undefined') {
scrollToBottom();
});
} else {
window.requestAnimationFrame(() => {
scrollToBottom();
});
}
}
}
};
void load();
}, [currentSessionId, loadMessages, messages, scrollToBottom]);
}, [currentSessionId, hasActiveAnchor, loadMessages, messages, scrollToBottom, sessionActivityPhase]);
if (!currentSessionId && !draftOpen) {
return (
@@ -44,7 +44,7 @@ const EMPTY_QUEUE: QueuedMessage[] = [];
interface ChatInputProps {
onOpenSettings?: () => void;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean }) => void;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean; clearAnchor?: boolean }) => void;
}
const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null;
@@ -63,7 +63,7 @@ interface ChatMessageProps {
};
onContentChange?: (reason?: ContentChangeReason) => void;
animationHandlers?: AnimationHandlers;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean }) => void;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean; clearAnchor?: boolean }) => void;
isPendingAnchor?: boolean;
turnGroupingContext?: TurnGroupingContext;
}
@@ -19,7 +19,7 @@ interface MessageListProps {
hasMoreAbove: boolean;
isLoadingOlder: boolean;
onLoadOlder: () => void;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean }) => void;
scrollToBottom?: (options?: { instant?: boolean; force?: boolean; clearAnchor?: boolean }) => void;
pendingAnchorId?: string | null;
}
@@ -42,6 +42,8 @@ export const MemoryDebugPanel: React.FC<MemoryDebugPanelProps> = ({ onClose }) =
isZombie: memoryState?.isZombie || false,
backgroundCount: memoryState?.backgroundMessageCount || 0,
lastAccessed: memoryState?.lastAccessedAt || 0,
activeTurnAnchorId: memoryState?.activeTurnAnchorId ?? null,
activeTurnSpacerHeight: memoryState?.activeTurnSpacerHeight ?? 0,
isCurrent: session.id === currentSessionId
};
}).sort((a, b) => b.lastAccessed - a.lastAccessed);
@@ -143,6 +145,11 @@ export const MemoryDebugPanel: React.FC<MemoryDebugPanelProps> = ({ onClose }) =
}`}>
{stat.messageCount} msgs
</span>
{stat.activeTurnAnchorId && stat.activeTurnSpacerHeight > 0 && (
<span className="font-mono text-xs text-primary">
anchor+{Math.round(stat.activeTurnSpacerHeight)}px
</span>
)}
{stat.backgroundCount > 0 && (
<span className="text-primary">+{stat.backgroundCount}</span>
)}