diff --git a/apps/tlon-web/src/app.tsx b/apps/tlon-web/src/app.tsx
index eb365ce2dc..23c3f470c1 100644
--- a/apps/tlon-web/src/app.tsx
+++ b/apps/tlon-web/src/app.tsx
@@ -677,6 +677,23 @@ function RoutedApp() {
const theme = useTheme();
const isDarkMode = useIsDark();
+ useEffect(() => {
+ const onFocus = () => {
+ useLocalState.setState({ inFocus: true });
+ };
+ window.addEventListener('focus', onFocus);
+
+ const onBlur = () => {
+ useLocalState.setState({ inFocus: false });
+ };
+ window.addEventListener('blur', onBlur);
+
+ return () => {
+ window.removeEventListener('focus', onFocus);
+ window.removeEventListener('blur', onBlur);
+ };
+ }, []);
+
useEffect(() => {
window.toggleDevTools = () => toggleDevTools();
}, []);
diff --git a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx
index c94e8b9171..9f754869f9 100644
--- a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx
+++ b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx
@@ -65,6 +65,7 @@ import {
useTrackedMessageStatus,
} from '@/state/chat';
import { useRouteGroup } from '@/state/groups';
+import { useInFocus } from '@/state/local';
import ReactionDetails from '../ChatReactions/ReactionDetails';
import {
@@ -219,6 +220,7 @@ const ChatMessage = React.memo<
[isMessageHidden, isPostHidden]
);
+ const inFocus = useInFocus();
const { ref: viewRef, inView } = useInView({
threshold: 1,
});
@@ -226,7 +228,7 @@ const ChatMessage = React.memo<
useEffect(() => {
const mainUnread =
unreadDisplay === 'top' || unreadDisplay === 'top-with-thread';
- if (!inView || !mainUnread) {
+ if (!inFocus || !inView || !mainUnread) {
return;
}
@@ -235,7 +237,14 @@ const ChatMessage = React.memo<
} else {
markReadChannel();
}
- }, [inView, unreadDisplay, isDMOrMultiDM, markReadChannel, markDmRead]);
+ }, [
+ inFocus,
+ inView,
+ unreadDisplay,
+ isDMOrMultiDM,
+ markReadChannel,
+ markDmRead,
+ ]);
const cacheId = {
author: window.our,
diff --git a/apps/tlon-web/src/chat/ChatMessage/DeletedChatMessage.tsx b/apps/tlon-web/src/chat/ChatMessage/DeletedChatMessage.tsx
index 954c57bfed..3a8a27d04e 100644
--- a/apps/tlon-web/src/chat/ChatMessage/DeletedChatMessage.tsx
+++ b/apps/tlon-web/src/chat/ChatMessage/DeletedChatMessage.tsx
@@ -11,6 +11,7 @@ import DateDivider from '@/chat/ChatMessage/DateDivider';
import { useMarkChannelRead } from '@/logic/channel';
import { useStickyUnread } from '@/logic/useStickyUnread';
import { useSourceActivity } from '@/state/activity';
+import { useInFocus } from '@/state/local';
export interface DeletedChatMessageProps {
whom: string;
@@ -61,17 +62,18 @@ const DeletedChatMessage = React.memo<
);
const { markRead: markReadChannel } = useMarkChannelRead(`chat/${whom}`);
+ const inFocus = useInFocus();
const { ref: viewRef, inView } = useInView({
threshold: 1,
});
useEffect(() => {
- if (!inView || !isUnread) {
+ if (!inFocus || !inView || !isUnread) {
return;
}
markReadChannel();
- }, [inView, isUnread, markReadChannel]);
+ }, [inFocus, inView, isUnread, markReadChannel]);
return (
isMessageHidden || isPostHidden,
[isMessageHidden, isPostHidden]
);
+ const inFocus = useInFocus();
const { ref: viewRef, inView } = useInView({
threshold: 1,
});
useEffect(() => {
// if no tracked unread we don't need to take any action
- if (!inView || !isUnread) {
+ if (!inFocus || !inView || !isUnread) {
return;
}
@@ -175,7 +177,15 @@ const ReplyMessage = React.memo<
} else {
markChannelRead();
}
- }, [whom, inView, isUnread, isDMOrMultiDM, markChannelRead, markDmRead]);
+ }, [
+ whom,
+ inFocus,
+ inView,
+ isUnread,
+ isDMOrMultiDM,
+ markChannelRead,
+ markDmRead,
+ ]);
const msgStatus = useTrackedMessageStatus({
author: window.our,
diff --git a/apps/tlon-web/src/state/activity.ts b/apps/tlon-web/src/state/activity.ts
index 64e4da996c..3cbf49ea88 100644
--- a/apps/tlon-web/src/state/activity.ts
+++ b/apps/tlon-web/src/state/activity.ts
@@ -2,7 +2,6 @@ import { useInfiniteQuery, useMutation } from '@tanstack/react-query';
import {
Activity,
ActivityAction,
- ActivityBundle,
ActivityDeleteUpdate,
ActivityFeed,
ActivityReadUpdate,
@@ -16,6 +15,7 @@ import {
Source,
VolumeMap,
VolumeSettings,
+ getKey,
sourceToString,
stripSourcePrefix,
} from '@tloncorp/shared/dist/urbit/activity';
@@ -23,10 +23,12 @@ import _ from 'lodash';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import api from '@/api';
+import { useChatStore } from '@/chat/useChatStore';
import useReactQueryScry from '@/logic/useReactQueryScry';
import { createDevLogger } from '@/logic/utils';
import queryClient from '@/queryClient';
+import { useLocalState } from './local';
import { SidebarFilter } from './settings';
const actLogger = createDevLogger('activity', false);
@@ -128,6 +130,22 @@ function activityVolumeUpdates(events: ActivityVolumeUpdate[]) {
}, {} as VolumeSettings);
}
+function optimisticActivityUpdate(d: Activity, source: string): Activity {
+ const old = d[source];
+ return {
+ ...d,
+ [source]: {
+ ...old,
+ unread: null,
+ count: Math.min(0, old.count - (old.unread?.count || 0)),
+ 'notify-count':
+ old.unread && old.unread.notify
+ ? Math.min(old['notify-count'] - old.unread.count)
+ : old['notify-count'],
+ },
+ };
+}
+
function updateActivity({
main,
threads,
@@ -135,10 +153,18 @@ function updateActivity({
main: Activity;
threads: Record;
}) {
+ const { current, atBottom } = useChatStore.getState();
+ const source = getKey(current);
+ const inFocus = useLocalState.getState().inFocus;
+ const filteredMain =
+ inFocus && atBottom && source in main
+ ? optimisticActivityUpdate(main, source)
+ : main;
+ console.log({ inFocus, source, atBottom, filteredMain });
queryClient.setQueryData(unreadsKey(), (d: Activity | undefined) => {
return {
...d,
- ...main,
+ ...filteredMain,
};
});
@@ -304,19 +330,7 @@ export function useMarkReadMutation(recursive = false) {
};
}
- const old = d[source];
- return {
- ...d,
- [source]: {
- ...old,
- unread: null,
- count: Math.min(0, old.count - (old.unread?.count || 0)),
- 'notify-count':
- old.unread && old.unread.notify
- ? Math.min(old['notify-count'] - old.unread.count)
- : old['notify-count'],
- },
- };
+ return optimisticActivityUpdate(d, source);
});
return { current };
diff --git a/apps/tlon-web/src/state/local.ts b/apps/tlon-web/src/state/local.ts
index 8f24d8a791..933c5201fe 100644
--- a/apps/tlon-web/src/state/local.ts
+++ b/apps/tlon-web/src/state/local.ts
@@ -22,6 +22,7 @@ interface LocalState {
errorCount: number;
airLockErrorCount: number;
lastReconnect: number;
+ inFocus: boolean;
onReconnect: (() => void) | null;
logs: string[];
log: (msg: string) => void;
@@ -44,6 +45,7 @@ export const useLocalState = create(
lastReconnect: Date.now(),
onReconnect: null,
logs: [],
+ inFocus: true,
log: (msg: string) => {
set(
produce((s) => {
@@ -107,3 +109,8 @@ const selLast = (s: LocalState) => s.lastReconnect;
export function useLastReconnect() {
return useLocalState(selLast);
}
+
+const selInFocus = (s: LocalState) => s.inFocus;
+export function useInFocus() {
+ return useLocalState(selInFocus);
+}