phase 4 foundations: vendor missing lexicons, refreshSession api, toLex no-op purge, de-agent link-meta

This commit is contained in:
Samuel Newman
2026-07-17 11:55:07 +03:00
parent d13d1ca604
commit d052d14f2b
80 changed files with 649 additions and 450 deletions
+30
View File
@@ -123,6 +123,10 @@
"app.bsky.notification.unregisterPush",
"app.bsky.notification.updateSeen",
"app.bsky.richtext.facet",
"app.bsky.unspecced.getSuggestedOnboardingUsers",
"app.bsky.unspecced.getSuggestedUsersForDiscover",
"app.bsky.unspecced.getSuggestedUsersForExplore",
"app.bsky.unspecced.getSuggestedUsersForSeeMore",
"app.bsky.video.defs",
"app.bsky.video.getJobStatus",
"app.bsky.video.getUploadLimits",
@@ -273,6 +277,8 @@
"com.atproto.sync.notifyOfUpdate",
"com.atproto.sync.requestCrawl",
"com.atproto.sync.subscribeRepos",
"com.atproto.temp.checkHandleAvailability",
"com.atproto.temp.checkSignupQueue",
"com.germnetwork.declaration"
],
"resolutions": {
@@ -800,6 +806,10 @@
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedFeedsSkeleton",
"cid": "bafyreihqvsdre7oxhlpl7yucsmpdupwakipyzsu2gjlkbufgmklcgr5cbq"
},
"app.bsky.unspecced.getSuggestedOnboardingUsers": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedOnboardingUsers",
"cid": "bafyreieot3hdk6h2enuivlp6m2jivd2y4npwbr4h7nsb3kutvnmtpkubm4"
},
"app.bsky.unspecced.getSuggestedStarterPacks": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedStarterPacks",
"cid": "bafyreih34c76u3vbjaaukyojpsrljslmnptzpnydizyfrjtedzmwosmawy"
@@ -812,6 +822,18 @@
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsers",
"cid": "bafyreib5qavwbqignxipudnk5jzs2inlkjxosckpxakorbk3erlscfn6ba"
},
"app.bsky.unspecced.getSuggestedUsersForDiscover": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersForDiscover",
"cid": "bafyreienm4jci3ri5umqmdyycpyhtwjbteynqx54tyfjdrilzg6kqr65dm"
},
"app.bsky.unspecced.getSuggestedUsersForExplore": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersForExplore",
"cid": "bafyreidje2onbwlnhm6st54evtxvvsc5n3al4vxq67maah24a2hvwvsd2m"
},
"app.bsky.unspecced.getSuggestedUsersForSeeMore": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersForSeeMore",
"cid": "bafyreidg6zyy4x2cskf7jfdhlmn4nps6ug7tzqhckpx3s264xlxrh5djji"
},
"app.bsky.unspecced.getSuggestedUsersSkeleton": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersSkeleton",
"cid": "bafyreibvcocfbsxbf2s2rzuvcovkoyq6m7yz3csxrd6mdeoiwmkjqucxke"
@@ -1456,6 +1478,14 @@
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.subscribeRepos",
"cid": "bafyreic6fm5atifiaq67u44dtxc3ufjbq3z7yysryjrzrw37hkb32yajbi"
},
"com.atproto.temp.checkHandleAvailability": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/com.atproto.temp.checkHandleAvailability",
"cid": "bafyreifanpe7ixmxampsbl3oaftbo24vzhe5pcunhf3xdsmrmgrgkq5ziq"
},
"com.atproto.temp.checkSignupQueue": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/com.atproto.temp.checkSignupQueue",
"cid": "bafyreignao6slhwqr5kqdiey243oy5wmewfkz4vzseoypdxzoyakwen7bm"
},
"com.germnetwork.declaration": {
"uri": "at://did:plc:qyqmmncrm6qx33kpy7vqndik/com.atproto.lexicon.schema/com.germnetwork.declaration",
"cid": "bafyreiekwjmxshje655olra4mata7npso7bbgytc4fop77ycjtrbewwi3y"
@@ -0,0 +1,49 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedOnboardingUsers",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for onboarding",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recId": {
"type": "string",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,41 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForDiscover",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the Discover page",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,46 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForDiscoverSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the Discover page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForDiscover",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,45 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForExplore",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the Explore page",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,50 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForExploreSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the Explore page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForExplore",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,45 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForSeeMore",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the See More page",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,50 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForSeeMoreSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the See More page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForSeeMore",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,88 @@
{
"lexicon": 1,
"id": "com.atproto.temp.checkHandleAvailability",
"defs": {
"main": {
"type": "query",
"description": "Checks whether the provided handle is available. If the handle is not available, available suggestions will be returned. Optional inputs will be used to generate suggestions.",
"parameters": {
"type": "params",
"required": ["handle"],
"properties": {
"handle": {
"type": "string",
"format": "handle",
"description": "Tentative handle. Will be checked for availability or used to build handle suggestions."
},
"email": {
"type": "string",
"description": "User-provided email. Might be used to build handle suggestions."
},
"birthDate": {
"type": "string",
"format": "datetime",
"description": "User-provided birth date. Might be used to build handle suggestions."
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["handle", "result"],
"properties": {
"handle": {
"type": "string",
"format": "handle",
"description": "Echo of the input handle."
},
"result": {
"type": "union",
"refs": ["#resultAvailable", "#resultUnavailable"]
}
}
}
},
"errors": [
{
"name": "InvalidEmail",
"description": "An invalid email was provided."
}
]
},
"resultAvailable": {
"type": "object",
"description": "Indicates the provided handle is available.",
"properties": {}
},
"resultUnavailable": {
"type": "object",
"description": "Indicates the provided handle is unavailable and gives suggestions of available handles.",
"required": ["suggestions"],
"properties": {
"suggestions": {
"type": "array",
"description": "List of suggested handles based on the provided inputs.",
"items": {
"type": "ref",
"ref": "#suggestion"
}
}
}
},
"suggestion": {
"type": "object",
"required": ["handle", "method"],
"properties": {
"handle": {
"type": "string",
"format": "handle"
},
"method": {
"type": "string",
"description": "Method used to build this suggestion. Should be considered opaque to clients. Can be used for metrics."
}
}
}
}
}
@@ -0,0 +1,22 @@
{
"lexicon": 1,
"id": "com.atproto.temp.checkSignupQueue",
"defs": {
"main": {
"type": "query",
"description": "Check accounts location in signup queue.",
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["activated"],
"properties": {
"activated": { "type": "boolean" },
"placeInQueue": { "type": "integer" },
"estimatedTimeMs": { "type": "integer" }
}
}
}
}
}
}
@@ -62,7 +62,6 @@ export function useAutocomplete({
key: profile.did,
type: 'profile' as const,
value: '@' + profile.handle,
// TODO(phase4): drop toLex once searchActorsTypeahead (bridge agent)
// emits #/lexicons views
profile: toLex<AutocompleteProfile['profile']>(profile),
}))
@@ -134,10 +133,7 @@ function moderateProfileItem({
item: AutocompleteProfile
moderationOpts: ModerationOpts
}) {
// TODO(phase4): drop toLex once searchActorsTypeahead emits #/lexicons views
const modui = moderateProfile(toLex(item.profile), moderationOpts).ui(
'profileList',
)
const modui = moderateProfile(item.profile, moderationOpts).ui('profileList')
const isExactMatch = query && item.profile.handle.toLowerCase() === query
if (
+1 -5
View File
@@ -17,7 +17,6 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type Layout = {
size: number
@@ -163,10 +162,7 @@ function AvatarBubble({
type="user"
hideLiveBadge
noBorder
// TODO(phase4): drop toLex once profile props emit #/lexicons views
moderation={moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)}
moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
/>
) : (
<AvatarPlaceholder size={size} />
+1 -3
View File
@@ -7,7 +7,6 @@ import {useProfilesQuery} from '#/state/queries/profile'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function AvatarStack({
profiles,
@@ -35,8 +34,7 @@ export function AvatarStack({
: profiles.map(item => ({
key: item.did,
profile: item,
// TODO(phase4): drop toLex once useProfilesQuery emits #/lexicons views
moderation: moderateProfile(toLex(item), moderationOpts),
moderation: moderateProfile(item, moderationOpts),
}))
return (
+1 -3
View File
@@ -11,7 +11,6 @@ import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
const AVI_SIZE = 30
const AVI_SIZE_SMALL = 20
@@ -96,8 +95,7 @@ function KnownFollowersInner({
const textStyle = [a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]
const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => {
// TODO(phase4): drop toLex once KnownFollowers emits #/lexicons views
const moderation = moderateProfile(toLex(f), moderationOpts)
const moderation = moderateProfile(f, moderationOpts)
return {
profile: {
...f,
+1 -3
View File
@@ -24,7 +24,6 @@ import * as Hider from '#/components/moderation/Hider'
import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
/*
* This component is based on `FeedCard` and is tightly coupled with that
@@ -55,8 +54,7 @@ export function Default(
const {view, showPinButton} = props
const moderationOpts = useModerationOpts()
const moderation = moderationOpts
? // TODO(phase4): drop toLex once ListView props emit #/lexicons views
moderateUserList(toLex(view), moderationOpts)
? moderateUserList(view, moderationOpts)
: undefined
return (
+1 -3
View File
@@ -20,7 +20,6 @@ import * as StarterPackCard from '#/components/StarterPack/StarterPackCard'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function NewskieDialog({
profile,
@@ -90,8 +89,7 @@ function DialogInner({
const profileName = useMemo(() => {
if (!moderationOpts) return profile.displayName || profile.handle
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
return sanitizeDisplayName(
profile.displayName || profile.handle,
moderation.ui('displayName'),
+1 -3
View File
@@ -5,7 +5,6 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {atoms as a, useTheme} from '#/alf'
import * as FeedCard from '#/components/FeedCard'
import {ContentHider} from '#/components/moderation/ContentHider'
import {toLex} from '#/types/bsky'
import {type EmbedType} from '#/types/bsky/post'
import {type CommonProps} from './types'
@@ -41,8 +40,7 @@ export function ModeratedFeedEmbed({
const moderationOpts = useModerationOpts()
const moderation = useMemo(() => {
return moderationOpts
? // TODO(phase4): drop toLex once feed embed view emits #/lexicons views
moderateFeedGenerator(toLex(embed.view), moderationOpts)
? moderateFeedGenerator(embed.view, moderationOpts)
: undefined
}, [embed.view, moderationOpts])
return (
+1 -3
View File
@@ -5,7 +5,6 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard'
import {ContentHider} from '#/components/moderation/ContentHider'
import {toLex} from '#/types/bsky'
import {type EmbedType} from '#/types/bsky/post'
import {type CommonProps} from './types'
@@ -31,8 +30,7 @@ export function ModeratedListEmbed({
const moderationOpts = useModerationOpts()
const moderation = useMemo(() => {
return moderationOpts
? // TODO(phase4): drop toLex once list embed view emits #/lexicons views
moderateUserList(toLex(embed.view), moderationOpts)
? moderateUserList(embed.view, moderationOpts)
: undefined
}, [embed.view, moderationOpts])
return (
+1 -3
View File
@@ -268,9 +268,7 @@ export function QuoteEmbed({
[embed],
)
const moderation = useMemo(() => {
return moderationOpts
? moderatePost(bsky.toLex(quote), moderationOpts)
: undefined
return moderationOpts ? moderatePost(quote, moderationOpts) : undefined
}, [quote, moderationOpts])
const t = useTheme()
@@ -22,7 +22,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {type chat} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function RecentChats({
postUri,
@@ -121,8 +120,7 @@ function RecentChatItem({
const primaryProfile = useProfileShadow(primaryMember)
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
const moderation = moderateProfile(toLex(primaryProfile), moderationOpts)
const moderation = moderateProfile(primaryProfile, moderationOpts)
const name =
convo.kind === 'group'
? convo.details.name
+6 -6
View File
@@ -44,7 +44,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {type Metrics} from '#/analytics'
import {useActorStatus} from '#/features/liveNow'
import * as bsky from '#/types/bsky'
import type * as bsky from '#/types/bsky'
export function Default({
profile,
@@ -170,7 +170,7 @@ export function Avatar({
liveOverride?: boolean
size?: number
}) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const {isActive: live} = useActorStatus(profile)
@@ -240,7 +240,7 @@ function InlineNameAndHandle({
moderationOpts: ModerationOpts
}) {
const t = useTheme()
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -294,7 +294,7 @@ export function Name({
style?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
}) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -479,7 +479,7 @@ export function FollowButtonInner({
}: FollowButtonProps) {
const {t: l} = useLingui()
const profile = useProfileShadow(profileUnshadowed)
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
profile,
logContext,
@@ -617,7 +617,7 @@ export function Labels({
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
}) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const modui = moderation.ui('profileList')
const followedBy = profile.viewer?.followedBy
@@ -39,7 +39,6 @@ import {IS_WEB_TOUCH_DEVICE} from '#/env'
import {useActorStatus} from '#/features/liveNow'
import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {type ProfileHoverCardProps} from './types'
const floatingMiddlewares = [
@@ -424,8 +423,7 @@ function Inner({
const {_, i18n} = useLingui()
const {currentAccount} = useSession()
const moderation = useMemo(
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
() => moderateProfile(toLex(profile), moderationOpts),
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
const [descriptionRT] = useRichText(profile.description ?? '')
@@ -26,7 +26,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
function WizardListCard({
type,
@@ -140,10 +139,7 @@ export function WizardProfileCard({
const included = isTarget || state.profiles.some(p => p.did === profile.did)
const disabled =
isTarget || (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderationUi = moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)
const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar')
const displayName = profile.displayName
? sanitizeDisplayName(profile.displayName)
: `@${sanitizeHandle(profile.handle)}`
@@ -194,11 +190,9 @@ export function WizardFeedCard({
const isDiscover = generator.uri === DISCOVER_FEED_URI
const included = isDiscover || state.feeds.some(f => f.uri === generator.uri)
const disabled = isDiscover || (!included && state.feeds.length >= 3)
// TODO(phase4): drop toLex once GeneratorView prop emits #/lexicons views
const moderationUi = moderateFeedGenerator(
toLex(generator),
moderationOpts,
).ui('avatar')
const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui(
'avatar',
)
const onPress = () => {
if (disabled) return
@@ -33,7 +33,6 @@ import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {AvatarBubbles} from '../AvatarBubbles'
import {Error} from '../Error'
import {ProfileBadges} from '../ProfileBadges'
@@ -411,8 +410,7 @@ function DefaultProfileCard({
const t = useTheme()
const {t: l} = useLingui()
const enabled = canBeMessaged(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const handle = sanitizeHandle(profile.handle, '@')
const displayName = createSanitizedDisplayName(
profile,
@@ -488,8 +486,7 @@ function ExistingChatCard({
: createSanitizedDisplayName(
convo.primaryMember,
true,
// TODO(phase4): drop toLex once convo primaryMember emits #/lexicons views
moderateProfile(toLex(convo.primaryMember), moderationOpts).ui(
moderateProfile(convo.primaryMember, moderationOpts).ui(
'displayName',
),
)
@@ -28,7 +28,6 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
const DISPLAY_NAME_MAX_GRAPHEMES = 64
const DESCRIPTION_MAX_GRAPHEMES = 300
@@ -165,9 +164,8 @@ function DialogInner({
// We want to be working with a blank state here, so let's get the
// serialized version and turn it back into a RichText
// TODO(phase4): drop toLex once the list view producer emits #/lexicons facets
const serialized = richTextToString(
new RichTextAPI({text, facets: toLex(facets)}),
new RichTextAPI({text, facets: facets}),
false,
)
+1 -3
View File
@@ -15,7 +15,6 @@ import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Ti
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type Props = {
testID?: string
@@ -90,8 +89,7 @@ function Tab({
const {t: l} = useLingui()
const moderationOpts = useModerationOpts()
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts!)
const moderation = moderateProfile(profile, moderationOpts!)
const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
+1 -3
View File
@@ -54,7 +54,6 @@ import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance'
import {IS_NATIVE, IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type NewGroupChatItem = {
type: 'newGroupChat'
@@ -1071,8 +1070,7 @@ function DefaultProfileCard({
const t = useTheme()
const {t: l} = useLingui()
const enabled = canBeMessaged(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const handle = sanitizeHandle(profile.handle, '@')
const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
+1 -3
View File
@@ -29,7 +29,6 @@ import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker'
import {canReact, hasReachedReactionLimit} from './util'
@@ -87,8 +86,7 @@ export let MessageContextMenu = ({
const str = richTextToString(
new RichText({
text: message.text,
// TODO(phase4): drop toLex once the message producer emits #/lexicons facets
facets: toLex(message.facets),
facets: message.facets,
}),
true,
)
+1 -3
View File
@@ -344,9 +344,7 @@ let MessageItem = ({
size={AVATAR_SIZE}
type={profile.associated?.labeler ? 'labeler' : 'user'}
onBeforePress={() => unstableCacheProfileView(queryClient, profile)}
moderation={moderateProfile(bsky.toLex(profile), moderationOpts).ui(
'avatar',
)}
moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
/>
) : (
<ProfileCard.AvatarPlaceholder size={AVATAR_SIZE} />
+1 -3
View File
@@ -21,7 +21,6 @@ import {Link} from '#/components/Link'
import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {IS_LIQUID_GLASS} from '#/env'
import {toLex} from '#/types/bsky'
import {type ConvoWithDetails} from './util'
const PFP_SIZE = 40
@@ -86,8 +85,7 @@ function ProfileHeaderReady({
const {t: l} = useLingui()
const profile = useProfileShadow(convo.primaryMember)
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const blockInfo = useMemo(() => {
const modui = moderation.ui('profileView')
@@ -10,7 +10,6 @@ import * as Toggle from '#/components/forms/Toggle'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function GroupChatProfileCard({
profile,
@@ -21,8 +20,7 @@ export function GroupChatProfileCard({
}) {
const t = useTheme()
const enabled = canBeAddedToGroup(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const handle = sanitizeHandle(profile.handle, '@')
const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
+1 -4
View File
@@ -154,10 +154,7 @@ export function canReact({
}
if (primaryMember && moderationOpts) {
const moderation = moderateProfile(
bsky.toLex(primaryMember),
moderationOpts,
)
const moderation = moderateProfile(primaryMember, moderationOpts)
if (convoState.convo.kind === 'direct') {
// Either direction (blocking or blocked-by) hides reactions in 1-1s
if (moderation.blocked) return false
@@ -395,11 +395,9 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
{createSanitizedDisplayName(
joinLinkPreview.owner,
true,
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
moderateProfile(
bsky.toLex(joinLinkPreview.owner),
moderationOpts,
).ui('displayName'),
moderateProfile(joinLinkPreview.owner, moderationOpts).ui(
'displayName',
),
)}
</InlineLinkText>
</Trans>
+1 -3
View File
@@ -16,7 +16,6 @@ import {
} from '#/components/moderation/LabelsOnMeDialog'
import * as Pills from '#/components/Pills'
import {type app, type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function PostAlerts({
post,
@@ -68,8 +67,7 @@ export function PostAlerts({
*/
const shownCauses = [...alerts, ...informs, ...modui.blurs]
const additionalLabels = filterUserFacingLabels(
// TODO(phase4): drop toLex once PostView labels are #/lexicons-typed
toLex(allLabels),
allLabels,
currentAccount?.did,
).filter(label =>
shownCauses.every(
@@ -24,21 +24,7 @@ export function parseReportSubject(
}
}
/*
* TODO(phase4): drop toLex once ReportSubject is a #/lexicons union. The view
* subjects are structurally the #/lexicons shapes; we bridge here so the
* `isType` schema guards below narrow against the lexicon `$type`s.
*/
const subject = bsky.toLex<
| app.bsky.actor.defs.ProfileViewBasic
| app.bsky.actor.defs.ProfileView
| app.bsky.actor.defs.ProfileViewDetailed
| app.bsky.actor.defs.StatusView
| app.bsky.graph.defs.ListView
| app.bsky.feed.defs.GeneratorView
| app.bsky.graph.defs.StarterPackView
| app.bsky.feed.defs.PostView
>(rawSubject)
const subject = rawSubject
if (
bsky.isType(app.bsky.actor.defs.profileViewBasic, subject) ||
@@ -15,7 +15,6 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {type app, com, tools} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function GoLiveDisabledDialog({
control,
@@ -60,7 +59,7 @@ export function DialogInner({
} else {
await pdsClient.call(
com.atproto.moderation.createReport,
toLex<com.atproto.moderation.createReport.$InputBody>({
{
reasonType: tools.ozone.report.defs.reasonAppeal.value,
subject: {
$type: 'com.atproto.repo.strongRef',
@@ -68,7 +67,7 @@ export function DialogInner({
cid: status.cid,
},
reason: details,
}),
},
{
service: api.moderation.service,
},
@@ -27,7 +27,7 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator'
import {type app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import type * as bsky from '#/types/bsky'
export function LiveStatusDialog({
control,
@@ -115,8 +115,7 @@ export function LiveStatus({
const dialogContext = Dialog.useDialogContext()
const moderation = useMemo(() => {
if (!moderationOpts) return undefined
// TODO(phase4): drop toLex once profile producers emit #/lexicons views
return moderateStatus(bsky.toLex(profile), moderationOpts)
return moderateStatus(profile, moderationOpts)
}, [profile, moderationOpts])
return (
+4 -11
View File
@@ -23,7 +23,7 @@ import {
useMaybeProfileShadow,
} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAgent, usePdsClient, useSession} from '#/state/session'
import {usePdsClient, useSession} from '#/state/session'
import {useTickEveryMinute} from '#/state/shell'
import {useDialogContext} from '#/components/Dialog'
import * as Toast from '#/components/Toast'
@@ -105,8 +105,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
const moderation = useMemo(() => {
if (!actor || !('status' in actor && actor.status)) return undefined
if (!moderationOpts) return undefined
// TODO(phase4): drop toLex once profile producers emit #/lexicons views
return moderateStatus(bsky.toLex(actor), moderationOpts)
return moderateStatus(actor, moderationOpts)
}, [actor, moderationOpts])
return useMemo(() => {
@@ -120,12 +119,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
}
if (shadowed && 'status' in shadowed && shadowed.status) {
/*
* TODO(phase4): drop toLex once profile-shadow emits #/lexicons views.
* The shadow store still holds old `@atproto/api` StatusViews, so brand
* it once here to feed the new-world validation and returned view.
*/
const status = bsky.toLex<app.bsky.actor.defs.StatusView>(shadowed.status)
const status = shadowed.status
const isValid = isStatusValidForViewers(status, config)
const isDisabled = status.isDisabled || false
const isActive = isStatusStillActive(status.expiresAt)
@@ -195,7 +189,6 @@ export function useLiveLinkMetaQuery(url: string | null) {
const liveNowConfig = useLiveNowConfig()
const {_} = useLingui()
const agent = useAgent()
return useQuery({
enabled: !!url,
queryKey: ['link-meta', url],
@@ -213,7 +206,7 @@ export function useLiveLinkMetaQuery(url: string | null) {
)
}
return await getLinkMeta(agent, url)
return await getLinkMeta(url)
},
})
}
+4 -9
View File
@@ -2,7 +2,6 @@ import {type Client} from '@atproto/lex-client'
import {DEMO_FEED} from '#/lib/demo'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {type FeedAPI, type FeedAPIResponse} from './types'
export class DemoFeedAPI implements FeedAPI {
@@ -12,17 +11,13 @@ export class DemoFeedAPI implements FeedAPI {
this.client = client
}
// eslint-disable-next-line @typescript-eslint/require-await
async peekLatest(): Promise<app.bsky.feed.defs.FeedViewPost> {
/*
* TODO(phase4): drop this cast once `#/lib/demo` (DEMO_FEED) sources its
* feed items from `#/lexicons`. Its records are still typed against the old
* `@atproto/api` FeedViewPost, which does not assign to the branded lexicon
* type this method must return.
*/
return toLex(DEMO_FEED.feed[0])
return DEMO_FEED.feed[0]
}
// eslint-disable-next-line @typescript-eslint/require-await
async fetch(): Promise<FeedAPIResponse> {
return toLex(DEMO_FEED)
return DEMO_FEED
}
}
+1 -8
View File
@@ -8,7 +8,6 @@ import {feedUriToHref} from '#/lib/strings/url-helpers'
import {getContentLanguages} from '#/state/preferences/languages'
import {type FeedParams} from '#/state/queries/post-feed'
import {app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {FeedTuner} from '../feed-manip'
import {type FeedTunerFn} from '../feed-manip'
import {
@@ -264,13 +263,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
return {
success: true,
cursor: res.cursor,
/*
* TODO(phase4): drop the toLex once `#/lib/api/feed-manip` (FeedTuner)
* flips its FeedViewPost source from `@atproto/api` to `#/lexicons`. Its
* `_feedPost` is still the old-world view type, which does not assign to
* the branded lexicon type this page carries.
*/
feed: slices.map(slice => toLex(slice._feedPost)),
feed: slices.map(slice => slice._feedPost),
}
}
}
+10 -35
View File
@@ -91,16 +91,7 @@ export async function post(
for (let i = 0; i < thread.posts.length; i++) {
const draft = thread.posts[i]
/*
* Not awaited to avoid waterfalls. `draft.richtext` is still an
* `@atproto/api` RichText (composer state is migrated by Task 7); the SDK
* RichText is structurally the same transplant, so bridge it here.
* TODO(phase4): drop toLex once composer state migrates to SDK RichText.
*/
const rtPromise = resolveRT(
resolveClients.appview,
bsky.toLex<RichText>(draft.richtext),
)
const rtPromise = resolveRT(resolveClients.appview, draft.richtext)
const embedPromise = resolveEmbed(
clients,
queryClient,
@@ -138,17 +129,11 @@ export async function post(
langs,
labels,
}
/*
* `value` is typed `LexMap` (a loose index-signature map). A generated
* record type is a valid LexMap at runtime but a strict interface is not
* assignable to an index-signature type, so widen with `toLex` at this
* boundary. Not a brand cast - the record is already fully typed.
*/
writes.push({
$type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.post',
rkey: rkey,
value: bsky.toLex(record),
value: record,
})
if (i === 0 && thread.threadgate.some(tg => tg.type !== 'everybody')) {
@@ -156,14 +141,10 @@ export async function post(
$type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.threadgate',
rkey: rkey,
value: bsky.toLex(
createThreadgateRecord({
post: uri,
allow: threadgateAllowUISettingToAllowRecordValue(
thread.threadgate,
),
}),
),
value: createThreadgateRecord({
post: uri,
allow: threadgateAllowUISettingToAllowRecordValue(thread.threadgate),
}),
})
}
@@ -175,12 +156,12 @@ export async function post(
$type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.postgate',
rkey: rkey,
value: bsky.toLex({
value: {
...thread.postgate,
$type: 'app.bsky.feed.postgate',
createdAt: now.toISOString(),
post: uri,
}),
},
})
}
@@ -426,7 +407,7 @@ async function resolveMedia(
* structural shape matches the lexicon blob field; the CID hasher handles
* both class instances and plain lex blobs (see computeCid).
*/
video: bsky.toLex(videoDraft.pendingPublish.blobRef),
video: videoDraft.pendingPublish.blobRef,
alt: videoDraft.altText || undefined,
captions: captions.length === 0 ? undefined : captions,
aspectRatio,
@@ -475,13 +456,7 @@ async function resolveMedia(
title: resolvedLink.title,
description: resolvedLink.description,
thumb: blob,
/*
* associatedRefs comes from getLinkMeta, still typed with the old
* `@atproto/api` StrongRef (link-meta.ts is out of scope). The shape
* is identical; bridge with toLex.
* TODO(phase4): drop toLex once link-meta migrates.
*/
associatedRefs: bsky.toLex(resolvedLink.associatedRefs),
associatedRefs: resolvedLink.associatedRefs,
},
}
}
+3 -15
View File
@@ -23,7 +23,6 @@ import {
import {type ComposerImage} from '#/state/gallery'
import {createComposerImage} from '#/state/gallery'
import {type ChatInvitePreview} from '#/state/queries/join-links'
import {type SessionAgent} from '#/state/session'
import {type Gif} from '#/features/gifPicker/types'
import {app, chat, com} from '#/lexicons'
import {createGIFDescription} from '../gif-alt-text'
@@ -31,13 +30,10 @@ import {createGIFDescription} from '../gif-alt-text'
/**
* Clients the link resolver needs. `appview` serves the `app.bsky.*` feed/graph
* reads plus handle resolution; `chat` serves the group join-link previews.
* `agent` is still threaded through to {@link getLinkMeta}, which has not yet
* migrated off the bridge - it only reads the (unused) service URL from it.
*/
export type ResolveClients = {
appview: Client
chat: Client
agent: SessionAgent
}
type ResolvedExternalLink = {
@@ -195,7 +191,7 @@ export async function resolveLink(
view: res.starterPack,
}
}
return resolveExternal(clients, uri)
return resolveExternal(uri)
// Forked from useGetPost. TODO: move into RQ.
async function getPost({uri}: {uri: string}) {
@@ -267,16 +263,8 @@ function getFileSlug(url: string | undefined): string | undefined {
return dotIndex > 0 ? filename.slice(0, dotIndex) : undefined
}
async function resolveExternal(
clients: ResolveClients,
uri: string,
): Promise<ResolvedExternalLink> {
/*
* getLinkMeta still takes the bridge agent (not yet migrated); it only reads
* a service URL from it that LINK_META_PROXY ignores. Keep threading the
* agent here until link-meta migrates.
*/
const result = await getLinkMeta(clients.agent, uri)
async function resolveExternal(uri: string): Promise<ResolvedExternalLink> {
const result = await getLinkMeta(uri)
return {
type: 'external',
uri: result.url,
+1 -5
View File
@@ -1,7 +1,6 @@
import {LINK_META_PROXY} from '#/lib/constants'
import {getGiphyMetaUri} from '#/lib/strings/embed-player'
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
import {type SessionAgent} from '#/state/session'
import {type app} from '#/lexicons'
import {isBskyAppUrl} from '../strings/url-helpers'
@@ -31,7 +30,6 @@ export interface LinkMeta {
}
export async function getLinkMeta(
agent: SessionAgent,
url: string,
timeout = 15e3,
): Promise<LinkMeta> {
@@ -80,9 +78,7 @@ export async function getLinkMeta(
try {
const response = await fetch(
`${LINK_META_PROXY(agent.serviceUrl.toString() || '')}${encodeURIComponent(
url,
)}`,
`${LINK_META_PROXY('')}${encodeURIComponent(url)}`,
{signal: controller.signal},
)
+2 -15
View File
@@ -12,7 +12,6 @@ import {
} from '#/lib/moderation/useGlobalLabelStrings'
import {useLabelDefinitions} from '#/state/preferences'
import {type app, type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
export interface LabelInfo {
label: com.atproto.label.defs.Label
@@ -23,26 +22,14 @@ export interface LabelInfo {
export function useLabelInfo(label: com.atproto.label.defs.Label): LabelInfo {
const {i18n} = useLingui()
/*
* TODO(phase4): drop the `toLex` casts once `useLabelDefinitions`
* (state/preferences/label-defs) emits `#/lexicons` / `@bsky.app/sdk`
* moderation types. It still returns old `@atproto/api`
* `InterpretedLabelValueDefinition` / `LabelerViewDetailed`, which are
* structurally identical to the SDK/lexicon ones modulo branded strings.
*/
const {labelDefs, labelers} = useLabelDefinitions()
const def = getDefinition(
toLex<Record<string, InterpretedLabelValueDefinition[]>>(labelDefs),
label,
)
const def = getDefinition(labelDefs, label)
const globalLabelStrings = useGlobalLabelStrings()
return {
label,
def,
strings: getLabelStrings(i18n.locale, globalLabelStrings, def),
labeler: toLex<app.bsky.labeler.defs.LabelerViewDetailed[]>(labelers).find(
labeler => label.src === labeler.creator.did,
),
labeler: labelers.find(labeler => label.src === labeler.creator.did),
}
}
@@ -13,8 +13,6 @@ import {type Props as SVGIconProps} from '#/components/icons/common'
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
import {type AppModerationCause} from '#/components/Pills'
import {type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {useGlobalLabelStrings} from './useGlobalLabelStrings'
import {getDefinition, getLabelStrings} from './useLabelInfo'
@@ -129,18 +127,7 @@ export function useModerationCauseDescription(
}
}
if (cause.type === 'label') {
/*
* TODO(phase4): drop `toLex` once `useLabelDefinitions` and the label
* cause type share the `#/lexicons` label shape. `labelDefs` still comes
* from the old `@atproto/api`-typed producer and `cause.label` is the SDK
* label; both are structurally identical to the `#/lexicons` label.
*/
const def =
cause.labelDef ||
getDefinition(
toLex<Parameters<typeof getDefinition>[0]>(labelDefs),
toLex<com.atproto.label.defs.Label>(cause.label),
)
const def = cause.labelDef || getDefinition(labelDefs, cause.label)
const strings = getLabelStrings(i18n.locale, globalLabelStrings, def)
const labeler = labelers.find(l => l.creator.did === cause.label.src)
let source = labeler
+2 -3
View File
@@ -46,7 +46,7 @@ import {Error} from '#/components/Error'
import * as Layout from '#/components/Layout'
import {IS_LIQUID_GLASS} from '#/env'
import {chat} from '#/lexicons'
import {isType, toLex} from '#/types/bsky'
import {isType} from '#/types/bsky'
import {ChatDisabled} from './components/ChatDisabled'
import {ChatEnded} from './components/ChatEnded'
import {ChatLocked} from './components/ChatLocked'
@@ -228,8 +228,7 @@ function InnerReady({
const moderationOpts = useModerationOpts()
const primaryMemberModeration = useMemo(() => {
if (!primaryMember || !moderationOpts) return null
// TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views
return moderateProfile(toLex(primaryMember), moderationOpts)
return moderateProfile(primaryMember, moderationOpts)
}, [primaryMember, moderationOpts])
const header = <MessagesListHeader convo={convo} />
@@ -21,7 +21,6 @@ import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
import {MemberMenu} from './MemberMenu'
import {RemoveMemberPrompt} from './prompts'
import {StatusBadge} from './StatusBadge'
@@ -81,8 +80,7 @@ export function Member({
return <MemberPlaceholder />
}
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount
@@ -108,13 +106,10 @@ export function Member({
}
const joinedReason = profile.kind?.addedBy
? // TODO(phase4): drop toLex once addedBy emits #/lexicons views
l`Added by ${createSanitizedDisplayName(
? l`Added by ${createSanitizedDisplayName(
profile.kind.addedBy,
true,
moderateProfile(toLex(profile.kind.addedBy), moderationOpts).ui(
'displayName',
),
moderateProfile(profile.kind.addedBy, moderationOpts).ui('displayName'),
)}`
: l`Added by invite link`
+4 -7
View File
@@ -187,9 +187,8 @@ export function JoinRequest({setScreenState}: Props) {
{createSanitizedDisplayName(
joinLinkPreview.owner,
true,
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
moderateProfile(
bsky.toLex(joinLinkPreview.owner),
joinLinkPreview.owner,
moderationOpts,
).ui('displayName'),
)}
@@ -224,14 +223,12 @@ export function JoinRequest({setScreenState}: Props) {
? l`Sign in to request access to this group chat.`
: l`Sign in to accept invite.`}{' '}
{joinLinkPreview.joinRule === 'followedByOwner' &&
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
l`Only people ${createSanitizedDisplayName(
joinLinkPreview.owner,
true,
moderateProfile(
bsky.toLex(joinLinkPreview.owner),
moderationOpts,
).ui('displayName'),
moderateProfile(joinLinkPreview.owner, moderationOpts).ui(
'displayName',
),
)} follows can join.`}
</Text>
<ActionButtons setScreenState={setScreenState} />
@@ -125,8 +125,7 @@ function DirectChatItem({
const {isWithinLeftPanel} = useIsWithinSplitView()
const moderation = useMemo(
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
() => moderateProfile(bsky.toLex(profile), moderationOpts),
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
@@ -193,10 +192,7 @@ function GroupChatItem({
const moderation = useMemo(
() =>
groupOwner
? // TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views
moderateProfile(bsky.toLex(groupOwner), moderationOpts)
: undefined,
groupOwner ? moderateProfile(groupOwner, moderationOpts) : undefined,
[groupOwner, moderationOpts],
)
@@ -423,15 +419,7 @@ function BaseChatItem({
for (const member of convo.view.members) {
unstableCacheProfileView(queryClient, member)
}
/*
* `convo.view` comes from the still-old-typed `#/components/dms/util`
* (migrates in a later task) while the convo cache is now keyed on the
* lexicon ConvoView. TODO(phase4): drop toLex once dms/util migrates.
*/
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>(convo.view),
)
precacheConvoQuery(queryClient, convo.view)
void decrementBadgeCount(convo.view.unreadCount)
if (isDeletedAccount) {
e.preventDefault()
@@ -525,10 +513,7 @@ function BaseChatItem({
}
onPressIn={() =>
// see onPress: old-typed dms/util view into the new-typed cache
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>(convo.view),
)
precacheConvoQuery(queryClient, convo.view)
}
onPress={onPress}
onLongPress={showMenu && IS_NATIVE ? onLongPress : undefined}
@@ -17,7 +17,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {usePromptControl} from '#/components/Prompt'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
@@ -115,8 +114,7 @@ function InviterHeader({
const t = useTheme()
const profile = useProfileShadow(profileUnshadowed)
const moderation = useMemo(
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
() => moderateProfile(toLex(profile), moderationOpts),
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
const displayName = createSanitizedDisplayName(
@@ -34,7 +34,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {type chat} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {CopyTextButton} from './CopyTextButton'
import {EditTextButton} from './EditTextButton'
@@ -69,8 +68,7 @@ export function InviteLinkDialog({
const ownerName = createSanitizedDisplayName(
owner,
false,
// TODO(phase4): drop toLex once GroupConvoMember emits #/lexicons views
moderateProfile(toLex(owner), moderationOpts).ui('displayName'),
moderateProfile(owner, moderationOpts).ui('displayName'),
)
const {joinLink} = convo.details
@@ -38,7 +38,6 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {type MessageEmbedState} from './MessageInputEmbed'
const MIN_HEIGHT = 40
@@ -70,15 +69,7 @@ export function MessageComposer({
const {getDraft, clearDraft} = useMessageDraft()
const composerInternalApiRef = useComposerInternalApiRef()
const {replyTo: replyToOld, clearReply} = useMessageReplies()
/*
* `useMessageReplies` (`#/components/dms`, migrates in a later task) still
* emits the old `@atproto/api` MessageView; the send path is typed on the
* lexicon view. Structurally identical modulo branded strings.
* TODO(phase4): drop toLex once dms/MessageReplies migrates.
*/
const replyTo = bsky.toLex<chat.bsky.convo.defs.MessageView | null>(
replyToOld,
)
const replyTo = replyToOld
const [text, setText] = useState(getDraft)
useSaveMessageDraft(text)
@@ -569,24 +569,18 @@ export function MessagesList({
try {
const post = await getPost({uri: embedState.uri})
if (post) {
/*
* `post` comes from the still-old-typed `useGetPost` producer
* (migrates in a later task), so its uri/view shapes carry plain
* strings where the lexicon types are branded. TODO(phase4): drop
* toLex once that producer migrates.
*/
embed = bsky.toLex<$Typed<app.bsky.embed.record.Main>>({
embed = {
$type: 'app.bsky.embed.record',
record: {
uri: post.uri,
cid: post.cid,
},
})
}
embedView = bsky.toLex<$Typed<app.bsky.embed.record.View>>({
embedView = {
$type: 'app.bsky.embed.record#view',
record: createEmbedViewRecordFromPost(post),
})
}
stripLinkFacet(uri => {
if (!isBskyPostUrl(uri)) return false
@@ -611,14 +605,10 @@ export function MessagesList({
const joinLinkPreview = await getJoinLinkPreview({code, hasSession})
if (joinLinkPreview) {
/*
* The preview comes from the still-old-typed `join-links.ts` query
* (migrates in a later task). TODO(phase4): drop toLex once it does.
*/
embedView = bsky.toLex<$Typed<chat.bsky.embed.joinLink.View>>({
embedView = {
$type: 'chat.bsky.embed.joinLink#view',
joinLinkPreview,
})
}
}
stripLinkFacet(uri => getChatInviteCodeFromUrl(uri) === code)
@@ -16,7 +16,6 @@ import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/P
import {ProfileBadges} from '#/components/ProfileBadges'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
export function MessagesListInfoPanel({
convo,
@@ -35,11 +34,10 @@ export function MessagesListInfoPanel({
)[0]
const handle = sanitizeHandle(profile.handle, '@')
const displayName = moderationOpts
? // TODO(phase4): drop toLex once convo members emit #/lexicons views
createSanitizedDisplayName(
? createSanitizedDisplayName(
profile,
true,
moderateProfile(toLex(profile), moderationOpts).ui('displayName'),
moderateProfile(profile, moderationOpts).ui('displayName'),
)
: handle
const profileLink =
@@ -41,7 +41,6 @@ import * as Menu from '#/components/Menu'
import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Toast from '#/components/Toast'
import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function RejectMenu({
convo,
@@ -229,18 +228,10 @@ export function AcceptChatButton({
onMutate: () => {
onAcceptConvo?.()
if (currentScreen === 'list') {
/*
* `convo` is the old-typed view threaded from `#/components/dms/util`
* consumers while the convo cache is now keyed on the lexicon
* ConvoView. TODO(phase4): drop toLex once this file's props flip.
*/
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>({
...convo,
status: 'accepted',
}),
)
precacheConvoQuery(queryClient, {
...convo,
status: 'accepted',
})
navigation.navigate('MessagesConversation', {
conversation: convo.id,
accept: true,
@@ -16,7 +16,6 @@ import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
const AVI_SIZE = 20
@@ -94,8 +93,7 @@ export function KnownLikers({post}: {post: AppBskyFeedDefs.PostView}) {
.map(like => like.actor)
.map(actor => ({
actor,
// TODO(phase4): drop toLex once useLikedBySampleQuery emits #/lexicons views
moderation: moderateProfile(toLex(actor), moderationOpts),
moderation: moderateProfile(actor, moderationOpts),
}))
.filter(({actor, moderation}) => {
const modui = moderation.ui('profileList')
@@ -33,7 +33,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog'
import {ProfileHeaderHandle} from './Handle'
@@ -67,7 +66,7 @@ let ProfileHeaderLabeler = ({
const isSelf = currentAccount?.did === profile.did
const moderation = useMemo(
() => moderateProfile(toLex(profile), moderationOpts),
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
@@ -40,7 +40,6 @@ import {IS_IOS, IS_NATIVE} from '#/env'
import {InviteFriendsDialog} from '#/features/inviteFriends'
import {useActorStatus} from '#/features/liveNow'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {GermButton} from '../components/GermButton'
import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog'
@@ -69,7 +68,7 @@ let ProfileHeaderStandard = ({
const {currentAccount} = useSession()
const {_} = useLingui()
const moderation = useMemo(
() => moderateProfile(toLex(profile), moderationOpts),
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
const [, queueUnblock] = useProfileBlockMutationQueue(profile)
+1 -3
View File
@@ -22,7 +22,6 @@ import {Header} from '#/components/Layout'
import * as ProfileCard from '#/components/ProfileCard'
import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {
HeaderLabelerButtons,
ProfileHeaderLabeler,
@@ -112,8 +111,7 @@ const MinimalHeader = memo(function MinimalHeader({
const profile = useProfileShadow(profileUnshadowed)
const moderationOpts = useModerationOpts()
const moderation = useMemo(
() =>
moderationOpts ? moderateProfile(toLex(profile), moderationOpts) : null,
() => (moderationOpts ? moderateProfile(profile, moderationOpts) : null),
[moderationOpts, profile],
)
const [visible, setVisible] = useState(false)
@@ -24,7 +24,6 @@ import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {MoreOptionsMenu} from './MoreOptionsMenu'
import {SubscribeMenu} from './SubscribeMenu'
@@ -130,8 +129,7 @@ export function Header({
list.description
? new RichTextAPI({
text: list.description,
// TODO(phase4): drop toLex once the list view producer emits #/lexicons facets
facets: toLex(list.descriptionFacets),
facets: list.descriptionFacets,
})
: undefined,
[list],
+1 -3
View File
@@ -39,7 +39,6 @@ import {Loader} from '#/components/Loader'
import * as Hider from '#/components/moderation/Hider'
import {IS_WEB} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {AboutSection} from './AboutSection'
import {ErrorScreen} from './components/ErrorScreen'
import {Header} from './components/Header'
@@ -167,8 +166,7 @@ function ProfileListScreenLoaded({
const [headerHeight, setHeaderHeight] = useState<number | null>(null)
const moderation = useMemo(() => {
// TODO(phase4): drop toLex once ListView prop emits #/lexicons views
return moderateUserList(toLex(list), moderationOpts)
return moderateUserList(list, moderationOpts)
}, [list, moderationOpts])
useSetTitle(isHidden ? _(msg`List Hidden`) : list.name)
@@ -22,7 +22,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function SearchHistory({
searchHistory,
@@ -207,8 +206,7 @@ function RecentProfileItem({
const {t: l} = useLingui()
const width = 80
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -29,7 +29,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import * as ModuleHeader from '../components/ModuleHeader'
import {toLex} from '#/types/bsky'
const IMAGE_SIZE = 56
@@ -307,8 +306,7 @@ function useModerateTrendingActors(
return actors
.filter(actor => {
// TODO(phase4): drop toLex once TrendView actors emit #/lexicons views
const decision = moderateProfile(toLex(actor), moderationOpts)
const decision = moderateProfile(actor, moderationOpts)
return !decision.ui('avatar').filter && !decision.ui('avatar').blur
})
.slice(0, 3)
+2 -7
View File
@@ -70,7 +70,6 @@ import {useActorStatus} from '#/features/liveNow'
import {type app} from '#/lexicons'
import {device, useStorage} from '#/storage'
import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged'
import {toLex} from '#/types/bsky'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export function SettingsScreen({}: Props) {
@@ -331,8 +330,7 @@ function ProfilePreview({
if (!moderationOpts) return null
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const moderation = moderateProfile(profile, moderationOpts)
const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -639,10 +637,7 @@ function AccountRow({
<UserAvatar
size={28}
avatar={profile.avatar}
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
moderation={moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)}
moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
type={profile.associated?.labeler ? 'labeler' : 'user'}
live={live}
hideLiveBadge
@@ -411,8 +411,7 @@ function Header({
const richText = record.description
? new RichTextAPI({
text: record.description,
// TODO(phase4): drop toLex once the starterpack record producer emits #/lexicons facets
facets: bsky.toLex(record.descriptionFacets),
facets: record.descriptionFacets,
})
: undefined
+1 -2
View File
@@ -808,8 +808,7 @@ function Overlay({
: undefined
const richText = new RichTextAPI({
text: record?.text || '',
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets
facets: bsky.toLex(record?.facets),
facets: record?.facets,
})
const handle = sanitizeHandle(post.author.handle, '@')
+4 -15
View File
@@ -205,12 +205,8 @@ export function useFeedPreviews(
if (item.isFallbackMarker) continue
const moderations = item.items.map(item =>
// TODO(phase4): drop toLex once feed-manip is migrated off
// @atproto/api and yields lex-typed slice items.
moderatePost(
bsky.toLex<app.bsky.feed.defs.PostView>(item.post),
moderationOpts!,
),
moderatePost(item.post, moderationOpts!),
)
// apply moderation filters
@@ -236,19 +232,12 @@ export function useFeedPreviews(
const feedPostSliceItem: FeedPostSliceItem = {
_reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`,
uri: subItem.post.uri,
// TODO(phase4): drop toLex once feed-manip is migrated
// off @atproto/api and yields lex-typed slice items.
post: bsky.toLex<app.bsky.feed.defs.PostView>(
subItem.post,
),
record: bsky.toLex<app.bsky.feed.post.Main>(
subItem.record,
),
post: subItem.post,
record: subItem.record,
moderation: moderations[i],
parentAuthor: subItem.parentAuthor
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>(
subItem.parentAuthor,
)
? subItem.parentAuthor
: undefined,
isParentBlocked: subItem.isParentBlocked,
isParentNotFound: subItem.isParentNotFound,
+11 -20
View File
@@ -57,33 +57,24 @@ export function useAddGroupMembers(
chat.bsky.actor.defs.ProfileViewBasic[]
>(listConvoMembersQueryKey(convoId))
/*
* The profile views come from producers that still emit the old
* `@atproto/api` shapes (useProfileQuery migrates in a later task), while
* the chat caches are now typed on the lexicon views. Structurally
* identical modulo branded strings. TODO(phase4): drop toLex once those
* producers migrate.
*/
const addedBy: chat.bsky.actor.defs.ProfileViewBasic | undefined =
myProfile
? bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({
? {
...myProfile,
$type: 'chat.bsky.actor.defs#profileViewBasic',
})
}
: undefined
const optimisticMembers: chat.bsky.actor.defs.ProfileViewBasic[] =
profiles.map(profile =>
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({
...profile,
$type: 'chat.bsky.actor.defs#profileViewBasic',
kind: {
$type: 'chat.bsky.actor.defs#groupConvoMember',
role: 'standard',
addedBy,
},
}),
)
profiles.map(profile => ({
...profile,
$type: 'chat.bsky.actor.defs#profileViewBasic',
kind: {
$type: 'chat.bsky.actor.defs#groupConvoMember',
role: 'standard',
addedBy,
},
}))
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
CONVO_KEY(convoId),
+1 -4
View File
@@ -297,11 +297,8 @@ export function* findAllPostsInQueryData(
if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) {
const quotedPost = getEmbeddedPost(item.subject?.embed)
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
// TODO(phase4): drop toLex once ../util is migrated and
// embedViewRecordToPostView returns the lexicon PostView.
yield bsky.toLex<app.bsky.feed.defs.PostView>(
embedViewRecordToPostView(quotedPost),
)
yield embedViewRecordToPostView(quotedPost)
}
}
}
+4 -12
View File
@@ -288,9 +288,8 @@ export function usePostFeedQuery(
.map(slice => {
const moderations = slice.items.map(item =>
moderatePost(
// TODO(phase4): drop toLex once feed-manip is migrated
// off @atproto/api and yields lex-typed slice items.
bsky.toLex<app.bsky.feed.defs.PostView>(item.post),
item.post,
moderationOpts!,
),
)
@@ -342,19 +341,12 @@ export function usePostFeedQuery(
const feedPostSliceItem: FeedPostSliceItem = {
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
uri: item.post.uri,
// TODO(phase4): drop toLex once feed-manip is migrated
// off @atproto/api and yields lex-typed slice items.
post: bsky.toLex<app.bsky.feed.defs.PostView>(
item.post,
),
record: bsky.toLex<app.bsky.feed.post.Main>(
item.record,
),
post: item.post,
record: item.record,
moderation: moderations[i],
parentAuthor: item.parentAuthor
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>(
item.parentAuthor,
)
? item.parentAuthor
: undefined,
isParentBlocked: item.isParentBlocked,
isParentNotFound: item.isParentNotFound,
+2 -11
View File
@@ -44,7 +44,6 @@ import {useAgeAssurance} from '#/ageAssurance'
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export * from '#/state/queries/preferences/const'
export * from '#/state/queries/preferences/moderation'
@@ -110,16 +109,8 @@ export function usePreferencesQuery() {
) {
data = {
...data,
/*
* TODO(phase4): drop the toLex bridges once
* `#/ageAssurance/util` (makeAgeRestrictedModerationPrefs) sources
* `ModerationPrefs` from `@bsky.app/sdk/moderation` instead of
* `@atproto/api`. The two shapes differ only in scalar branding
* (e.g. MutedWord.actorTarget's UnknownString), so the values are
* structurally interchangeable at this boundary.
*/
moderationPrefs: toLex(
makeAgeRestrictedModerationPrefs(toLex(data.moderationPrefs)),
moderationPrefs: makeAgeRestrictedModerationPrefs(
data.moderationPrefs,
),
}
}
+4 -6
View File
@@ -7,7 +7,7 @@ import {
resolveLink,
} from '#/lib/api/resolve'
import {STALE} from '#/state/queries/index'
import {useAgent, useChatClient, useLexClient} from '#/state/session'
import {useChatClient, useLexClient} from '#/state/session'
import {type Gif} from '#/features/gifPicker/types'
export const RQKEY_LINK_ROOT = 'resolve-link'
@@ -26,15 +26,13 @@ export function resolveLinkQueryOptions(clients: ResolveClients, url: string) {
/**
* Bundle the clients the link resolver needs from the session hooks. The
* appview client serves the `app.bsky.*` reads and handle resolution, the chat
* client serves group join-link previews, and the bridge agent is still passed
* through to the not-yet-migrated `getLinkMeta`.
* appview client serves the `app.bsky.*` reads and handle resolution, and the
* chat client serves group join-link previews.
*/
export function useResolveClients(): ResolveClients {
const appview = useLexClient()
const chat = useChatClient()
const agent = useAgent()
return {appview, chat, agent}
return {appview, chat}
}
export function useResolveLinkQuery(url: string) {
+7 -19
View File
@@ -255,27 +255,23 @@ export function* getThreadPlaceholderCandidates(
for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(post)
}
/*
* TODO(phase4): drop toLex once the feed/quote/search/bookmarks/explore
* `findAllPostsInQueryData` generators are migrated to `#/lexicons` types.
*/
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post))
yield postViewToThreadPlaceholder(post)
}
for (let post of findAllPostsInQuoteQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post))
yield postViewToThreadPlaceholder(post)
}
for (let post of findAllPostsInSearchQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post))
yield postViewToThreadPlaceholder(post)
}
for (let post of findAllPostsInBookmarksQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post))
yield postViewToThreadPlaceholder(post)
}
for (let post of findAllPostsInExploreFeedPreviewsQueryData(
queryClient,
uri,
)) {
yield postViewToThreadPlaceholder(bsky.toLex(post))
yield postViewToThreadPlaceholder(post)
}
}
@@ -302,11 +298,7 @@ export function* findAllPostsInQueryData(
const qp = getEmbeddedPost(item.value.post.embed)
if (qp && didOrHandleUriMatches(atUri, qp)) {
/*
* TODO(phase4): drop toLex once `embedViewRecordToPostView` in
* `#/state/queries/util` is migrated to `#/lexicons` types.
*/
yield bsky.toLex(embedViewRecordToPostView(qp))
yield embedViewRecordToPostView(qp)
}
}
}
@@ -335,11 +327,7 @@ export function* findAllProfilesInQueryData(
const qp = getEmbeddedPost(item.value.post.embed)
if (qp && qp.author.did === did) {
/*
* TODO(phase4): drop toLex once `getEmbeddedPost` in
* `#/state/queries/util` is migrated to `#/lexicons` types.
*/
yield bsky.toLex(qp.author)
yield qp.author
}
}
}
+25
View File
@@ -80,6 +80,7 @@ const ApiContext = createContext<SessionApiContext>({
resumeSession: async () => {},
removeAccount: () => {},
partialRefreshSession: async () => {},
refreshSession: () => Promise.resolve(undefined),
})
ApiContext.displayName = 'SessionApiContext'
@@ -344,6 +345,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
})
}, [store, state, cancelPendingTask])
const refreshSession = useCallback<
SessionApiContext['refreshSession']
>(async () => {
const bundle = store.getState().currentAgentState.agent as unknown as
| SessionBundle
| PublicSessionBundle
if (!bundle.session) return undefined // logged out: nothing to refresh
/*
* PasswordSession.refresh() re-runs com.atproto.server.refreshSession +
* getSession. On success the session's onUpdated hook fires, which the
* armed makeSessionHooks wiring maps to an 'update' event; the reducer
* snapshots the refreshed account. No explicit dispatch is needed here.
* The returned snapshot lets callers read post-refresh fields without
* waiting on the (async) reducer update.
*/
await bundle.session.refresh()
return sessionDataToSessionAccount(
bundle.session.session,
bundle.session.session.service,
)
}, [store])
const removeAccount = useCallback<SessionApiContext['removeAccount']>(
account => {
addSessionDebugLog({
@@ -443,6 +466,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
resumeSession,
removeAccount,
partialRefreshSession,
refreshSession,
}),
[
createAccount,
@@ -452,6 +476,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
resumeSession,
removeAccount,
partialRefreshSession,
refreshSession,
],
)
+11
View File
@@ -51,4 +51,15 @@ export type SessionApiContext = {
* `persistSessionHandler`.
*/
partialRefreshSession: () => Promise<void>
/**
* Force a full session refresh (re-runs `com.atproto.server.refreshSession`
* plus `getSession`) and return the refreshed account snapshot, or
* `undefined` when logged out.
*
* The refresh routes through the session's own `refresh()`, whose success
* hook propagates the updated account into state; the returned snapshot lets
* callers read post-refresh fields synchronously without waiting on the
* (async) reducer update. Rejections propagate to the caller.
*/
refreshSession: () => Promise<SessionAccount | undefined>
}
+1 -3
View File
@@ -12,7 +12,6 @@ import {mimeToExt} from '#/lib/media/video/util'
import {shortenLinks} from '#/lib/strings/rich-text-manip'
import {type ComposerImage} from '#/state/gallery'
import {threadgateAllowUISettingToAllowRecordValue} from '#/state/queries/threadgate/util'
import {createPublicAgent} from '#/state/session/agent'
import {getPublicLexClient} from '#/state/session/clients'
import {
type ComposerState,
@@ -141,10 +140,9 @@ async function postDraftToServerPost(
// Add quote record embed
if (post.embed.quote) {
const agent = createPublicAgent()
const publicClient = getPublicLexClient()
const resolved = await resolveLink(
{appview: publicClient, chat: publicClient, agent},
{appview: publicClient, chat: publicClient},
post.embed.quote.uri,
)
if (resolved && resolved.type === 'record') {
@@ -148,15 +148,12 @@ let NotificationFeedItem = ({
{
profile: item.notification.author,
href: makeProfileLink(item.notification.author),
moderation: moderateProfile(
bsky.toLex(item.notification.author),
moderationOpts,
),
moderation: moderateProfile(item.notification.author, moderationOpts),
},
...(item.additional?.map(({author}) => ({
profile: author,
href: makeProfileLink(author),
moderation: moderateProfile(bsky.toLex(author), moderationOpts),
moderation: moderateProfile(author, moderationOpts),
})) || []),
].filter(
(author, index, arr) =>
+1 -2
View File
@@ -73,8 +73,7 @@ export function PostQuotes({uri}: {uri: string}) {
) {
return null
}
// TODO(phase4): drop toLex once usePostQuotesQuery emits #/lexicons views
const moderation = moderatePost(bsky.toLex(post), moderationOpts)
const moderation = moderatePost(post, moderationOpts)
return {post, record: post.record, moderation}
}),
)
+3 -8
View File
@@ -61,17 +61,13 @@ export function Post({
record
? new RichTextAPI({
text: record.text,
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets
facets: bsky.toLex(record.facets),
facets: record.facets,
})
: undefined,
[record],
)
const moderation = useMemo(
() =>
moderationOpts
? moderatePost(bsky.toLex(post), moderationOpts)
: undefined,
() => (moderationOpts ? moderatePost(post, moderationOpts) : undefined),
[moderationOpts, post],
)
if (postShadowed === POST_TOMBSTONE) {
@@ -135,8 +131,7 @@ function PostInner({
text: record.text,
author: post.author,
embed: post.embed,
// TODO(phase4): drop toLex once the composer state accepts SDK ModerationDecision
moderation: bsky.toLex(moderation),
moderation: moderation,
langs: record.langs,
},
logContext: 'PostReply',
+1 -2
View File
@@ -99,8 +99,7 @@ export function PostFeedItem({
() =>
new RichTextAPI({
text: record.text,
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets
facets: bsky.toLex(record.facets),
facets: record.facets,
}),
[record],
)