fix convo list cache updates from chat log events
- use continue instead of return when a convo isn't found in cache, so the rest of the event batch isn't dropped (the bus advances its cursor past the batch, so dropped logs are never redelivered) - on logAcceptConvo, flip status to accepted in every cache holding the convo, including 'all'-status caches like the always-mounted unread query; previously the stale copy kept status 'request' and the next logCreateMessage could resurrect the convo in the requests inbox - add a rev guard so log-driven updates skip when the log isn't newer than the cached convo, preventing stale refetch snapshots and log events from double-applying against each other Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -282,8 +282,11 @@ export function ListConvosProviderInner({
|
|||||||
mutateMembers(convoId, list =>
|
mutateMembers(convoId, list =>
|
||||||
list.some(m => m.did === did) ? list : list.concat(newMember),
|
list.some(m => m.did === did) ? list : list.concat(newMember),
|
||||||
)
|
)
|
||||||
mutateConvoView(convoId, convo =>
|
mutateConvoView(
|
||||||
addMemberToConvoView(convo, newMember, rev, alreadyKnownMember),
|
convoId,
|
||||||
|
withRevGuard(rev, convo =>
|
||||||
|
addMemberToConvoView(convo, newMember, rev, alreadyKnownMember),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,8 +304,11 @@ export function ListConvosProviderInner({
|
|||||||
>(listConvoMembersQueryKey(convoId))
|
>(listConvoMembersQueryKey(convoId))
|
||||||
?.some(m => m.did === did) === false
|
?.some(m => m.did === did) === false
|
||||||
mutateMembers(convoId, list => list.filter(m => m.did !== did))
|
mutateMembers(convoId, list => list.filter(m => m.did !== did))
|
||||||
mutateConvoView(convoId, convo =>
|
mutateConvoView(
|
||||||
removeMemberFromConvoView(convo, did, rev, alreadyRemovedMember),
|
convoId,
|
||||||
|
withRevGuard(rev, convo =>
|
||||||
|
removeMemberFromConvoView(convo, did, rev, alreadyRemovedMember),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,24 +323,27 @@ export function ListConvosProviderInner({
|
|||||||
// link preview so its viewer state reflects the lost membership.
|
// link preview so its viewer state reflects the lost membership.
|
||||||
void invalidateJoinLinkPreviewsForConvo(queryClient, log.convoId)
|
void invalidateJoinLinkPreviewsForConvo(queryClient, log.convoId)
|
||||||
} else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) {
|
} else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) {
|
||||||
updateConvoInAllLists(log.convoId, convo => {
|
updateConvoInAllLists(
|
||||||
if (
|
log.convoId,
|
||||||
(ChatBskyConvoDefs.isDeletedMessageView(log.message) ||
|
withRevGuard(log.rev, convo => {
|
||||||
ChatBskyConvoDefs.isMessageView(log.message)) &&
|
if (
|
||||||
(ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) ||
|
(ChatBskyConvoDefs.isDeletedMessageView(log.message) ||
|
||||||
ChatBskyConvoDefs.isMessageView(convo.lastMessage))
|
ChatBskyConvoDefs.isMessageView(log.message)) &&
|
||||||
) {
|
(ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) ||
|
||||||
return log.message.id === convo.lastMessage.id
|
ChatBskyConvoDefs.isMessageView(convo.lastMessage))
|
||||||
? {
|
) {
|
||||||
...convo,
|
return log.message.id === convo.lastMessage.id
|
||||||
rev: log.rev,
|
? {
|
||||||
lastMessage: log.message,
|
...convo,
|
||||||
}
|
rev: log.rev,
|
||||||
: convo
|
lastMessage: log.message,
|
||||||
} else {
|
}
|
||||||
return convo
|
: convo
|
||||||
}
|
} else {
|
||||||
})
|
return convo
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogCreateMessage(log)) {
|
} else if (ChatBskyConvoDefs.isLogCreateMessage(log)) {
|
||||||
// Store in a new var to avoid TS errors due to closures.
|
// Store in a new var to avoid TS errors due to closures.
|
||||||
const logRef: ChatBskyConvoDefs.LogCreateMessage = log
|
const logRef: ChatBskyConvoDefs.LogCreateMessage = log
|
||||||
@@ -356,9 +365,20 @@ export function ListConvosProviderInner({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!foundConvo) {
|
if (!foundConvo) {
|
||||||
// Convo not found, trigger refetch
|
// Convo not found, trigger refetch. Use continue (not return) so
|
||||||
|
// the remaining logs in this batch still apply - the bus advances
|
||||||
|
// its cursor past this batch, so a dropped log is never
|
||||||
|
// redelivered.
|
||||||
debouncedRefetch()
|
debouncedRefetch()
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rev guard. updatedConvo is built once from foundConvo and applied
|
||||||
|
// across caches, so guarding here (rather than per-cache) is both
|
||||||
|
// simplest and correct - skip if the log isn't newer than the
|
||||||
|
// cached convo.
|
||||||
|
if (logRef.rev <= foundConvo.rev) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// add relatedProfiles to members list, but making sure to dedupe
|
// add relatedProfiles to members list, but making sure to dedupe
|
||||||
@@ -449,17 +469,23 @@ export function ListConvosProviderInner({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (ChatBskyConvoDefs.isLogReadMessage(log)) {
|
} else if (ChatBskyConvoDefs.isLogReadMessage(log)) {
|
||||||
updateConvoInAllLists(log.convoId, convo => ({
|
updateConvoInAllLists(
|
||||||
...convo,
|
log.convoId,
|
||||||
unreadCount: 0,
|
withRevGuard(log.rev, convo => ({
|
||||||
rev: log.rev,
|
...convo,
|
||||||
}))
|
unreadCount: 0,
|
||||||
|
rev: log.rev,
|
||||||
|
})),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogReadConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogReadConvo(log)) {
|
||||||
updateConvoInAllLists(log.convoId, convo => ({
|
updateConvoInAllLists(
|
||||||
...convo,
|
log.convoId,
|
||||||
unreadCount: 0,
|
withRevGuard(log.rev, convo => ({
|
||||||
rev: log.rev,
|
...convo,
|
||||||
}))
|
unreadCount: 0,
|
||||||
|
rev: log.rev,
|
||||||
|
})),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogAcceptConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogAcceptConvo(log)) {
|
||||||
const requestQueries =
|
const requestQueries =
|
||||||
queryClient.getQueriesData<ConvoListQueryData>({
|
queryClient.getQueriesData<ConvoListQueryData>({
|
||||||
@@ -472,13 +498,37 @@ export function ListConvosProviderInner({
|
|||||||
if (foundConvo) break
|
if (foundConvo) break
|
||||||
}
|
}
|
||||||
if (!foundConvo) {
|
if (!foundConvo) {
|
||||||
|
// Use continue (not return) so the remaining logs in this batch
|
||||||
|
// still apply - the bus advances its cursor past this batch, so a
|
||||||
|
// dropped log is never redelivered.
|
||||||
debouncedRefetch()
|
debouncedRefetch()
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
if (log.rev <= foundConvo.rev) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
const acceptedConvo: ChatBskyConvoDefs.ConvoView = {
|
const acceptedConvo: ChatBskyConvoDefs.ConvoView = {
|
||||||
...foundConvo,
|
...foundConvo,
|
||||||
status: 'accepted',
|
status: 'accepted',
|
||||||
|
rev: log.rev,
|
||||||
}
|
}
|
||||||
|
// Flip status to 'accepted' in every cache that already holds this
|
||||||
|
// convo - including 'all'-status caches like the provider's
|
||||||
|
// always-mounted unread query, which the request->accepted move
|
||||||
|
// below otherwise never touches. Without this the stale 'all' copy
|
||||||
|
// keeps status: 'request', and the next isLogCreateMessage can seed
|
||||||
|
// foundConvo from it and resurrect the convo in the requests inbox.
|
||||||
|
// Runs before the delete-from-request below: it updates in place
|
||||||
|
// (never inserts), so the 'request' caches get the accepted copy and
|
||||||
|
// are then cleared by the delete, leaving no stale request entries.
|
||||||
|
updateConvoInAllLists(
|
||||||
|
log.convoId,
|
||||||
|
withRevGuard(log.rev, convo => ({
|
||||||
|
...convo,
|
||||||
|
status: 'accepted',
|
||||||
|
rev: log.rev,
|
||||||
|
})),
|
||||||
|
)
|
||||||
queryClient.setQueriesData(
|
queryClient.setQueriesData(
|
||||||
{queryKey: RQKEY_PARTIAL('request')},
|
{queryKey: RQKEY_PARTIAL('request')},
|
||||||
(old?: ConvoListQueryData) => optimisticDelete(log.convoId, old),
|
(old?: ConvoListQueryData) => optimisticDelete(log.convoId, old),
|
||||||
@@ -519,60 +569,75 @@ export function ListConvosProviderInner({
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogMuteConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogMuteConvo(log)) {
|
||||||
mutateConvoView(log.convoId, convo => ({
|
mutateConvoView(
|
||||||
...convo,
|
log.convoId,
|
||||||
muted: true,
|
withRevGuard(log.rev, convo => ({
|
||||||
rev: log.rev,
|
...convo,
|
||||||
}))
|
muted: true,
|
||||||
|
rev: log.rev,
|
||||||
|
})),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogUnmuteConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogUnmuteConvo(log)) {
|
||||||
mutateConvoView(log.convoId, convo => ({
|
mutateConvoView(
|
||||||
...convo,
|
log.convoId,
|
||||||
muted: false,
|
withRevGuard(log.rev, convo => ({
|
||||||
rev: log.rev,
|
...convo,
|
||||||
}))
|
muted: false,
|
||||||
|
rev: log.rev,
|
||||||
|
})),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogLockConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogLockConvo(log)) {
|
||||||
mutateConvoView(log.convoId, convo => {
|
mutateConvoView(
|
||||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
log.convoId,
|
||||||
return {
|
withRevGuard(log.rev, convo => {
|
||||||
...convo,
|
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||||
kind: {...convo.kind, lockStatus: 'locked'},
|
return {
|
||||||
rev: log.rev,
|
...convo,
|
||||||
|
kind: {...convo.kind, lockStatus: 'locked'},
|
||||||
|
rev: log.rev,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return {...convo, rev: log.rev}
|
||||||
return {...convo, rev: log.rev}
|
}),
|
||||||
})
|
)
|
||||||
// The log event doesn't say whether the lock is forced by a
|
// The log event doesn't say whether the lock is forced by a
|
||||||
// moderation override, so refetch to pick up the flag.
|
// moderation override, so refetch to pick up the flag.
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: CONVO_KEY(log.convoId),
|
queryKey: CONVO_KEY(log.convoId),
|
||||||
})
|
})
|
||||||
} else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) {
|
||||||
mutateConvoView(log.convoId, convo => {
|
mutateConvoView(
|
||||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
log.convoId,
|
||||||
return {
|
withRevGuard(log.rev, convo => {
|
||||||
...convo,
|
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||||
kind: {
|
return {
|
||||||
...convo.kind,
|
...convo,
|
||||||
lockStatus: 'unlocked',
|
kind: {
|
||||||
// An unlocked convo cannot be moderation-locked.
|
...convo.kind,
|
||||||
lockStatusModerationOverride: false,
|
lockStatus: 'unlocked',
|
||||||
},
|
// An unlocked convo cannot be moderation-locked.
|
||||||
rev: log.rev,
|
lockStatusModerationOverride: false,
|
||||||
|
},
|
||||||
|
rev: log.rev,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return {...convo, rev: log.rev}
|
||||||
return {...convo, rev: log.rev}
|
}),
|
||||||
})
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogLockConvoPermanently(log)) {
|
} else if (ChatBskyConvoDefs.isLogLockConvoPermanently(log)) {
|
||||||
mutateConvoView(log.convoId, convo => {
|
mutateConvoView(
|
||||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
log.convoId,
|
||||||
return {
|
withRevGuard(log.rev, convo => {
|
||||||
...convo,
|
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||||
kind: {...convo.kind, lockStatus: 'locked-permanently'},
|
return {
|
||||||
rev: log.rev,
|
...convo,
|
||||||
|
kind: {...convo.kind, lockStatus: 'locked-permanently'},
|
||||||
|
rev: log.rev,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return {...convo, rev: log.rev}
|
||||||
return {...convo, rev: log.rev}
|
}),
|
||||||
})
|
)
|
||||||
} else if (
|
} else if (
|
||||||
ChatBskyConvoDefs.isLogCreateJoinLink(log) ||
|
ChatBskyConvoDefs.isLogCreateJoinLink(log) ||
|
||||||
ChatBskyConvoDefs.isLogEditJoinLink(log) ||
|
ChatBskyConvoDefs.isLogEditJoinLink(log) ||
|
||||||
@@ -592,30 +657,39 @@ export function ListConvosProviderInner({
|
|||||||
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
||||||
// single-convo cache updates too, keeping the in-convo requests
|
// single-convo cache updates too, keeping the in-convo requests
|
||||||
// banner in sync.
|
// banner in sync.
|
||||||
mutateConvoView(log.convoId, convo =>
|
mutateConvoView(
|
||||||
applyJoinRequestCountDelta(convo, log.rev, -1),
|
log.convoId,
|
||||||
|
withRevGuard(log.rev, convo =>
|
||||||
|
applyJoinRequestCountDelta(convo, log.rev, -1),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogIncomingJoinRequest(log)) {
|
} else if (ChatBskyConvoDefs.isLogIncomingJoinRequest(log)) {
|
||||||
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
// Route through mutateConvoView (not updateConvoInAllLists) so the
|
||||||
// single-convo cache updates too, letting the in-convo requests
|
// single-convo cache updates too, letting the in-convo requests
|
||||||
// banner appear live.
|
// banner appear live.
|
||||||
mutateConvoView(log.convoId, convo =>
|
mutateConvoView(
|
||||||
applyJoinRequestCountDelta(convo, log.rev, 1),
|
log.convoId,
|
||||||
|
withRevGuard(log.rev, convo =>
|
||||||
|
applyJoinRequestCountDelta(convo, log.rev, 1),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogReadJoinRequests(log)) {
|
} else if (ChatBskyConvoDefs.isLogReadJoinRequests(log)) {
|
||||||
// The owner marked join requests as read (possibly on another
|
// The owner marked join requests as read (possibly on another
|
||||||
// device). Zero the unread count but keep the total, mirroring the
|
// device). Zero the unread count but keep the total, mirroring the
|
||||||
// useMarkJoinRequestsRead mutation.
|
// useMarkJoinRequestsRead mutation.
|
||||||
mutateConvoView(log.convoId, convo => {
|
mutateConvoView(
|
||||||
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
log.convoId,
|
||||||
return {...convo, rev: log.rev}
|
withRevGuard(log.rev, convo => {
|
||||||
}
|
if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||||
return {
|
return {...convo, rev: log.rev}
|
||||||
...convo,
|
}
|
||||||
kind: {...convo.kind, unreadJoinRequestCount: 0},
|
return {
|
||||||
rev: log.rev,
|
...convo,
|
||||||
}
|
kind: {...convo.kind, unreadJoinRequestCount: 0},
|
||||||
})
|
rev: log.rev,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogOutgoingJoinRequest(log)) {
|
} else if (ChatBskyConvoDefs.isLogOutgoingJoinRequest(log)) {
|
||||||
// Viewer isn't in the chat yet, but the inbox surfaces outgoing
|
// Viewer isn't in the chat yet, but the inbox surfaces outgoing
|
||||||
// requests, so refetch to pick up the new entry.
|
// requests, so refetch to pick up the new entry.
|
||||||
@@ -623,8 +697,11 @@ export function ListConvosProviderInner({
|
|||||||
} else if (ChatBskyConvoDefs.isLogWithdrawIncomingJoinRequest(log)) {
|
} else if (ChatBskyConvoDefs.isLogWithdrawIncomingJoinRequest(log)) {
|
||||||
// A requester rescinded their request to a group the viewer owns.
|
// A requester rescinded their request to a group the viewer owns.
|
||||||
// Mirror of isLogIncomingJoinRequest: decrement the counts.
|
// Mirror of isLogIncomingJoinRequest: decrement the counts.
|
||||||
mutateConvoView(log.convoId, convo =>
|
mutateConvoView(
|
||||||
applyJoinRequestCountDelta(convo, log.rev, -1),
|
log.convoId,
|
||||||
|
withRevGuard(log.rev, convo =>
|
||||||
|
applyJoinRequestCountDelta(convo, log.rev, -1),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogWithdrawOutgoingJoinRequest(log)) {
|
} else if (ChatBskyConvoDefs.isLogWithdrawOutgoingJoinRequest(log)) {
|
||||||
// The viewer rescinded their own outgoing join request (possibly on
|
// The viewer rescinded their own outgoing join request (possibly on
|
||||||
@@ -634,25 +711,28 @@ export function ListConvosProviderInner({
|
|||||||
old => optimisticDeleteJoinRequest(log.convoId, old),
|
old => optimisticDeleteJoinRequest(log.convoId, old),
|
||||||
)
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogAddReaction(log)) {
|
} else if (ChatBskyConvoDefs.isLogAddReaction(log)) {
|
||||||
updateConvoInAllLists(log.convoId, convo => {
|
updateConvoInAllLists(
|
||||||
// add relatedProfiles to members list, but making sure to dedupe
|
log.convoId,
|
||||||
const relatedProfilesSansMembers = (
|
withRevGuard(log.rev, convo => {
|
||||||
log.relatedProfiles ?? []
|
// add relatedProfiles to members list, but making sure to dedupe
|
||||||
).filter(
|
const relatedProfilesSansMembers = (
|
||||||
profile =>
|
log.relatedProfiles ?? []
|
||||||
!convo.members.some(member => member.did === profile.did),
|
).filter(
|
||||||
)
|
profile =>
|
||||||
return {
|
!convo.members.some(member => member.did === profile.did),
|
||||||
...convo,
|
)
|
||||||
members: [...convo.members, ...relatedProfilesSansMembers],
|
return {
|
||||||
lastReaction: {
|
...convo,
|
||||||
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
members: [...convo.members, ...relatedProfilesSansMembers],
|
||||||
reaction: log.reaction,
|
lastReaction: {
|
||||||
message: log.message,
|
$type: 'chat.bsky.convo.defs#messageAndReactionView',
|
||||||
},
|
reaction: log.reaction,
|
||||||
rev: log.rev,
|
message: log.message,
|
||||||
}
|
},
|
||||||
})
|
rev: log.rev,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else if (ChatBskyConvoDefs.isLogAddMember(log)) {
|
} else if (ChatBskyConvoDefs.isLogAddMember(log)) {
|
||||||
const data = log.message.data
|
const data = log.message.data
|
||||||
if (
|
if (
|
||||||
@@ -725,31 +805,35 @@ export function ListConvosProviderInner({
|
|||||||
queryClient.setQueriesData(
|
queryClient.setQueriesData(
|
||||||
{queryKey: [RQKEY_ROOT]},
|
{queryKey: [RQKEY_ROOT]},
|
||||||
(old?: ConvoListQueryData) =>
|
(old?: ConvoListQueryData) =>
|
||||||
optimisticUpdate(log.convoId, old, convo => {
|
optimisticUpdate(
|
||||||
if (
|
log.convoId,
|
||||||
// if the convo is the same
|
old,
|
||||||
log.convoId === convo.id &&
|
withRevGuard(log.rev, convo => {
|
||||||
ChatBskyConvoDefs.isMessageAndReactionView(
|
if (
|
||||||
convo.lastReaction,
|
// if the convo is the same
|
||||||
) &&
|
log.convoId === convo.id &&
|
||||||
ChatBskyConvoDefs.isMessageView(log.message) &&
|
ChatBskyConvoDefs.isMessageAndReactionView(
|
||||||
// ...and the message is the same
|
convo.lastReaction,
|
||||||
convo.lastReaction.message.id === log.message.id &&
|
) &&
|
||||||
// ...and the reaction is the same
|
ChatBskyConvoDefs.isMessageView(log.message) &&
|
||||||
convo.lastReaction.reaction.sender.did ===
|
// ...and the message is the same
|
||||||
log.reaction.sender.did &&
|
convo.lastReaction.message.id === log.message.id &&
|
||||||
convo.lastReaction.reaction.value === log.reaction.value
|
// ...and the reaction is the same
|
||||||
) {
|
convo.lastReaction.reaction.sender.did ===
|
||||||
return {
|
log.reaction.sender.did &&
|
||||||
...convo,
|
convo.lastReaction.reaction.value === log.reaction.value
|
||||||
// ...remove the reaction. hopefully they didn't react twice in a row!
|
) {
|
||||||
lastReaction: undefined,
|
return {
|
||||||
rev: log.rev,
|
...convo,
|
||||||
|
// ...remove the reaction. hopefully they didn't react twice in a row!
|
||||||
|
lastReaction: undefined,
|
||||||
|
rev: log.rev,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return convo
|
||||||
}
|
}
|
||||||
} else {
|
}),
|
||||||
return convo
|
),
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -905,6 +989,25 @@ export function useOnMarkAsRead() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a log-driven convo update so it's skipped when the log is not newer
|
||||||
|
* than the cached convo. Lists are fed by two unsynchronized channels (full
|
||||||
|
* listConvos refetches and the log stream), so a stale refetch snapshot can be
|
||||||
|
* written after a log already applied, or a refetch can already include a
|
||||||
|
* message whose log then arrives and double-counts. Rev comparison as plain
|
||||||
|
* string comparison is safe here - revs are fixed-width. ConvoView.rev is a
|
||||||
|
* required field per the lexicon, so convo.rev always exists.
|
||||||
|
*
|
||||||
|
* Only used in the log-event paths (which have a log.rev), never in the generic
|
||||||
|
* optimisticUpdate helper, which mutations without a rev also call.
|
||||||
|
*/
|
||||||
|
function withRevGuard(
|
||||||
|
rev: string,
|
||||||
|
fn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView,
|
||||||
|
): (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView {
|
||||||
|
return convo => (rev <= convo.rev ? convo : fn(convo))
|
||||||
|
}
|
||||||
|
|
||||||
function optimisticUpdate(
|
function optimisticUpdate(
|
||||||
chatId: string,
|
chatId: string,
|
||||||
old?: ConvoListQueryData,
|
old?: ConvoListQueryData,
|
||||||
|
|||||||
Reference in New Issue
Block a user