fix(app): derive unread from an ack timestamp floor (#2717)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-12 01:40:36 +02:00
1 parent e1bc6c2f7e
commit 6d600990fe
19 files changed
+307 -460

No files matched your search

@@ -29,8 +29,6 @@ import {
resolveChannelMessagesWindowStatus,
selectChannelMessagesFillerVisible,
selectChannelMessagesSpacerHeight,
selectChannelMessagesTailGapId,
selectChannelMessagesTailProbeId,
selectChannelMessagesWindowBar,
} from '@app/features/messaging/state/ChannelMessagesLoadStateMachine';
import MessageEdit from '@app/features/messaging/state/MessageEdit';
@@ -68,11 +66,7 @@ import {clsx} from 'clsx';
import {runInAction} from 'mobx';
import {observer, useLocalObservable} from 'mobx-react-lite';
import type React from 'react';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
const TAIL_PROBE_MAX_ATTEMPTS = 2;
const TAIL_PROBE_MIN_INTERVAL_MS = 10_000;
const tailProbeAttemptedAt = new Map<string, number>();
import {useCallback, useEffect, useMemo, useRef} from 'react';
const MESSAGE_LIST_FOR_DESCRIPTOR = msg({
message: 'Message list for {channelName}',
@@ -173,9 +167,6 @@ export const Messages = observer(function Messages({
const scrollerContainerRef = useRef<HTMLDivElement | null>(null);
const lastStateSnapshotRef = useRef<MessagesStateSnapshot | null>(null);
const recoveryFetchChannelIdRef = useRef<string | null>(null);
const tailProbeKeyRef = useRef<string | null>(null);
const tailProbeAttemptsRef = useRef<{key: string; attempts: number} | null>(null);
const [settledTailProbeKey, setSettledTailProbeKey] = useState<string | null>(null);
interface MessageState extends MessagesStateSnapshot {
highlightedMessageId: string | null;
isAtBottom: boolean;
@@ -211,17 +202,6 @@ export const Messages = observer(function Messages({
});
const windowBar = selectChannelMessagesWindowBar(windowStatus);
const windowNeedsPage = windowStatus.needsPage;
const tailInput = {
status: windowStatus,
loading: safeMessages.loadingMore || safeMessages.probeLoading,
newestLoadedMessageId: safeMessages.last()?.id ?? null,
knownLatestMessageId: state.lastReadStateMessageId,
};
const tailWatermarkMessageId = state.lastReadStateMessageId;
const tailGapMessageId = selectChannelMessagesTailGapId(tailInput);
const tailProbeMessageId = selectChannelMessagesTailProbeId(tailInput);
const tailGapKey = tailGapMessageId == null ? null : `${tailGapMessageId}:${tailWatermarkMessageId}`;
const tailProbeKey = tailProbeMessageId == null ? null : `${tailProbeMessageId}:${tailWatermarkMessageId}`;
const canAutoAck = shouldAutoAck({
channelActive: allowAutoAck,
windowFocused: isWindowFocused,
@@ -474,47 +454,6 @@ export const Messages = observer(function Messages({
}
});
}, [channel.id, isGatewayConnected, selectedChannelId, windowNeedsPage, state.messageVersion]);
useEffect(() => {
if (!isGatewayConnected) {
tailProbeKeyRef.current = null;
return;
}
if (tailProbeMessageId == null || tailProbeKey == null || tailWatermarkMessageId == null) {
return;
}
if (selectedChannelId !== channel.id || tailProbeKeyRef.current === tailProbeKey) {
return;
}
const lastAttemptAt = tailProbeAttemptedAt.get(tailProbeKey);
if (lastAttemptAt != null && Date.now() - lastAttemptAt < TAIL_PROBE_MIN_INTERVAL_MS) {
return;
}
tailProbeKeyRef.current = tailProbeKey;
tailProbeAttemptedAt.set(tailProbeKey, Date.now());
void MessageCommands.fetchMessages(channel.id, null, tailProbeMessageId, MAX_MESSAGES_PER_CHANNEL, undefined, {
tailProbe: {
watermarkMessageId: tailWatermarkMessageId,
onSettled: (settlement) => {
if (settlement === 'applied') {
setSettledTailProbeKey(tailProbeKey);
return;
}
if (settlement === 'failed') {
const attempts =
tailProbeAttemptsRef.current?.key === tailProbeKey ? tailProbeAttemptsRef.current.attempts : 0;
if (attempts >= TAIL_PROBE_MAX_ATTEMPTS) {
setSettledTailProbeKey(tailProbeKey);
return;
}
tailProbeAttemptsRef.current = {key: tailProbeKey, attempts: attempts + 1};
}
if (tailProbeKeyRef.current === tailProbeKey) {
tailProbeKeyRef.current = null;
}
},
},
});
}, [channel.id, isGatewayConnected, selectedChannelId, tailProbeKey, tailProbeMessageId, tailWatermarkMessageId]);
useMessageListKeyboardNavigation({
containerRef: scrollManager.ref,
channelId: channel.id,
@@ -548,12 +487,11 @@ export const Messages = observer(function Messages({
};
}, []);
useEffect(() => {
if (!canAutoAck || !state.isAtBottom || !state.messages?.ready) return;
if (tailGapKey != null && settledTailProbeKey !== tailGapKey) return;
if (!canAutoAck || !state.isAtBottom || !state.messages?.ready || state.messages.loadingMore) return;
if (ReadStates.hasUnread(channel.id)) {
ReadStateCommands.ackWithStickyUnread(channel.id);
}
}, [canAutoAck, state.isAtBottom, state.messages?.ready, tailGapKey, settledTailProbeKey, channel.id]);
}, [canAutoAck, state.isAtBottom, state.messages?.ready, state.messages?.loadingMore, channel.id]);
useEffect(() => {
return () => {
const readState = ReadStates.getIfExists(channel.id);