extract resolveAllowGroupInvites helper, fix optimistic cache

Address review feedback: dedupe the allowGroupInvites fallback chain into
a single helper next to canBeAddedToGroup, used by the settings display,
the save path, and onMutate. The optimistic cache now stores the same
resolved value the server receives, so cache and persisted record stay
aligned before the next profile refetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-06-05 17:57:54 +03:00
parent 764e84557d
commit aad42020bb
3 changed files with 39 additions and 17 deletions
+16
View File
@@ -47,6 +47,22 @@ export function canBeAddedToGroup(profile: bsky.profile.AnyProfileView) {
} }
} }
/**
* Resolves the effective `allowGroupInvites` value for a chat declaration.
* When unset, group invites follow the general DM preference
* (`allowIncoming`), which itself defaults to `following`. This mirrors the
* `undefined` fallthrough in canBeAddedToGroup, and is the single source of
* truth for both displaying and persisting the setting.
*/
export function resolveAllowGroupInvites(
chat: {allowIncoming?: string; allowGroupInvites?: string} | undefined,
): 'all' | 'none' | 'following' {
return (chat?.allowGroupInvites ?? chat?.allowIncoming ?? 'following') as
| 'all'
| 'none'
| 'following'
}
export function localDateString(date: Date) { export function localDateString(date: Date) {
// can't use toISOString because it should be in local time // can't use toISOString because it should be in local time
const mm = date.getMonth() const mm = date.getMonth()
+2 -9
View File
@@ -13,6 +13,7 @@ import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen
import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {Divider} from '#/components/Divider' import {Divider} from '#/components/Divider'
import {resolveAllowGroupInvites} from '#/components/dms/util'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell' import {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell'
import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car' import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car'
@@ -199,15 +200,7 @@ export function MessagesSettingsScreenInner({}: Props) {
<Toggle.Group <Toggle.Group
label={l`Allow group chat invites from`} label={l`Allow group chat invites from`}
type="radio" type="radio"
values={[ values={[resolveAllowGroupInvites(profile?.associated?.chat)]}
(profile?.associated?.chat
?.allowGroupInvites as AllowIncoming) ??
// when unset, group invites follow the general DM
// preference (see canBeAddedToGroup)
(profile?.associated?.chat
?.allowIncoming as AllowIncoming) ??
'following',
]}
onChange={onSelectGroupInvitesFrom}> onChange={onSelectGroupInvitesFrom}>
<View> <View>
{allowGroupInvitesFromOptions.map(option => ( {allowGroupInvitesFromOptions.map(option => (
@@ -7,6 +7,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
import {resolveAllowGroupInvites} from '#/components/dms/util'
import {RQKEY as PROFILE_RKEY} from '../profile' import {RQKEY as PROFILE_RKEY} from '../profile'
export function useUpdateActorDeclaration({ export function useUpdateActorDeclaration({
@@ -34,12 +35,12 @@ export function useUpdateActorDeclaration({
update.allowIncoming ?? update.allowIncoming ??
current?.associated?.chat?.allowIncoming ?? current?.associated?.chat?.allowIncoming ??
'following' 'following'
const allowGroupInvites = const allowGroupInvites = resolveAllowGroupInvites({
update.allowGroupInvites ?? allowIncoming,
current?.associated?.chat?.allowGroupInvites ?? allowGroupInvites:
// when unset, group invites follow the general DM preference update.allowGroupInvites ??
// (see canBeAddedToGroup), so mirror that when persisting current?.associated?.chat?.allowGroupInvites,
allowIncoming })
const result = await agent.com.atproto.repo.putRecord({ const result = await agent.com.atproto.repo.putRecord({
repo: currentAccount.did, repo: currentAccount.did,
collection: 'chat.bsky.actor.declaration', collection: 'chat.bsky.actor.declaration',
@@ -58,14 +59,26 @@ export function useUpdateActorDeclaration({
PROFILE_RKEY(currentAccount?.did), PROFILE_RKEY(currentAccount?.did),
(old?: AppBskyActorDefs.ProfileViewDetailed) => { (old?: AppBskyActorDefs.ProfileViewDetailed) => {
if (!old) return old if (!old) return old
const allowIncoming =
update.allowIncoming ??
old.associated?.chat?.allowIncoming ??
'following'
// resolve the same concrete value the server will receive, so
// optimistic cache and persisted record stay aligned
const allowGroupInvites = resolveAllowGroupInvites({
allowIncoming,
allowGroupInvites:
update.allowGroupInvites ??
old.associated?.chat?.allowGroupInvites,
})
return { return {
...old, ...old,
associated: { associated: {
...old.associated, ...old.associated,
chat: { chat: {
allowIncoming: 'following',
...old.associated?.chat, ...old.associated?.chat,
...update, allowIncoming,
allowGroupInvites,
}, },
}, },
} satisfies AppBskyActorDefs.ProfileViewDetailed } satisfies AppBskyActorDefs.ProfileViewDetailed