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.unregisterPush",
"app.bsky.notification.updateSeen", "app.bsky.notification.updateSeen",
"app.bsky.richtext.facet", "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.defs",
"app.bsky.video.getJobStatus", "app.bsky.video.getJobStatus",
"app.bsky.video.getUploadLimits", "app.bsky.video.getUploadLimits",
@@ -273,6 +277,8 @@
"com.atproto.sync.notifyOfUpdate", "com.atproto.sync.notifyOfUpdate",
"com.atproto.sync.requestCrawl", "com.atproto.sync.requestCrawl",
"com.atproto.sync.subscribeRepos", "com.atproto.sync.subscribeRepos",
"com.atproto.temp.checkHandleAvailability",
"com.atproto.temp.checkSignupQueue",
"com.germnetwork.declaration" "com.germnetwork.declaration"
], ],
"resolutions": { "resolutions": {
@@ -800,6 +806,10 @@
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedFeedsSkeleton", "uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedFeedsSkeleton",
"cid": "bafyreihqvsdre7oxhlpl7yucsmpdupwakipyzsu2gjlkbufgmklcgr5cbq" "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": { "app.bsky.unspecced.getSuggestedStarterPacks": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedStarterPacks", "uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedStarterPacks",
"cid": "bafyreih34c76u3vbjaaukyojpsrljslmnptzpnydizyfrjtedzmwosmawy" "cid": "bafyreih34c76u3vbjaaukyojpsrljslmnptzpnydizyfrjtedzmwosmawy"
@@ -812,6 +822,18 @@
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsers", "uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsers",
"cid": "bafyreib5qavwbqignxipudnk5jzs2inlkjxosckpxakorbk3erlscfn6ba" "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": { "app.bsky.unspecced.getSuggestedUsersSkeleton": {
"uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersSkeleton", "uri": "at://did:plc:placeholder00000000000000000000/com.atproto.lexicon.schema/app.bsky.unspecced.getSuggestedUsersSkeleton",
"cid": "bafyreibvcocfbsxbf2s2rzuvcovkoyq6m7yz3csxrd6mdeoiwmkjqucxke" "cid": "bafyreibvcocfbsxbf2s2rzuvcovkoyq6m7yz3csxrd6mdeoiwmkjqucxke"
@@ -1456,6 +1478,14 @@
"uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.subscribeRepos", "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.subscribeRepos",
"cid": "bafyreic6fm5atifiaq67u44dtxc3ufjbq3z7yysryjrzrw37hkb32yajbi" "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": { "com.germnetwork.declaration": {
"uri": "at://did:plc:qyqmmncrm6qx33kpy7vqndik/com.atproto.lexicon.schema/com.germnetwork.declaration", "uri": "at://did:plc:qyqmmncrm6qx33kpy7vqndik/com.atproto.lexicon.schema/com.germnetwork.declaration",
"cid": "bafyreiekwjmxshje655olra4mata7npso7bbgytc4fop77ycjtrbewwi3y" "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, key: profile.did,
type: 'profile' as const, type: 'profile' as const,
value: '@' + profile.handle, value: '@' + profile.handle,
// TODO(phase4): drop toLex once searchActorsTypeahead (bridge agent)
// emits #/lexicons views // emits #/lexicons views
profile: toLex<AutocompleteProfile['profile']>(profile), profile: toLex<AutocompleteProfile['profile']>(profile),
})) }))
@@ -134,10 +133,7 @@ function moderateProfileItem({
item: AutocompleteProfile item: AutocompleteProfile
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
// TODO(phase4): drop toLex once searchActorsTypeahead emits #/lexicons views const modui = moderateProfile(item.profile, moderationOpts).ui('profileList')
const modui = moderateProfile(toLex(item.profile), moderationOpts).ui(
'profileList',
)
const isExactMatch = query && item.profile.handle.toLowerCase() === query const isExactMatch = query && item.profile.handle.toLowerCase() === query
if ( if (
+1 -5
View File
@@ -17,7 +17,6 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type Layout = { type Layout = {
size: number size: number
@@ -163,10 +162,7 @@ function AvatarBubble({
type="user" type="user"
hideLiveBadge hideLiveBadge
noBorder noBorder
// TODO(phase4): drop toLex once profile props emit #/lexicons views moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
moderation={moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)}
/> />
) : ( ) : (
<AvatarPlaceholder size={size} /> <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 {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function AvatarStack({ export function AvatarStack({
profiles, profiles,
@@ -35,8 +34,7 @@ export function AvatarStack({
: profiles.map(item => ({ : profiles.map(item => ({
key: item.did, key: item.did,
profile: item, profile: item,
// TODO(phase4): drop toLex once useProfilesQuery emits #/lexicons views moderation: moderateProfile(item, moderationOpts),
moderation: moderateProfile(toLex(item), moderationOpts),
})) }))
return ( return (
+1 -3
View File
@@ -11,7 +11,6 @@ import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
const AVI_SIZE = 30 const AVI_SIZE = 30
const AVI_SIZE_SMALL = 20 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 textStyle = [a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]
const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => { const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => {
// TODO(phase4): drop toLex once KnownFollowers emits #/lexicons views const moderation = moderateProfile(f, moderationOpts)
const moderation = moderateProfile(toLex(f), moderationOpts)
return { return {
profile: { profile: {
...f, ...f,
+1 -3
View File
@@ -24,7 +24,6 @@ import * as Hider from '#/components/moderation/Hider'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
/* /*
* This component is based on `FeedCard` and is tightly coupled with that * This component is based on `FeedCard` and is tightly coupled with that
@@ -55,8 +54,7 @@ export function Default(
const {view, showPinButton} = props const {view, showPinButton} = props
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const moderation = moderationOpts const moderation = moderationOpts
? // TODO(phase4): drop toLex once ListView props emit #/lexicons views ? moderateUserList(view, moderationOpts)
moderateUserList(toLex(view), moderationOpts)
: undefined : undefined
return ( return (
+1 -3
View File
@@ -20,7 +20,6 @@ import * as StarterPackCard from '#/components/StarterPack/StarterPackCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function NewskieDialog({ export function NewskieDialog({
profile, profile,
@@ -90,8 +89,7 @@ function DialogInner({
const profileName = useMemo(() => { const profileName = useMemo(() => {
if (!moderationOpts) return profile.displayName || profile.handle if (!moderationOpts) return profile.displayName || profile.handle
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
return sanitizeDisplayName( return sanitizeDisplayName(
profile.displayName || profile.handle, profile.displayName || profile.handle,
moderation.ui('displayName'), 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 {atoms as a, useTheme} from '#/alf'
import * as FeedCard from '#/components/FeedCard' import * as FeedCard from '#/components/FeedCard'
import {ContentHider} from '#/components/moderation/ContentHider' import {ContentHider} from '#/components/moderation/ContentHider'
import {toLex} from '#/types/bsky'
import {type EmbedType} from '#/types/bsky/post' import {type EmbedType} from '#/types/bsky/post'
import {type CommonProps} from './types' import {type CommonProps} from './types'
@@ -41,8 +40,7 @@ export function ModeratedFeedEmbed({
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const moderation = useMemo(() => { const moderation = useMemo(() => {
return moderationOpts return moderationOpts
? // TODO(phase4): drop toLex once feed embed view emits #/lexicons views ? moderateFeedGenerator(embed.view, moderationOpts)
moderateFeedGenerator(toLex(embed.view), moderationOpts)
: undefined : undefined
}, [embed.view, moderationOpts]) }, [embed.view, moderationOpts])
return ( return (
+1 -3
View File
@@ -5,7 +5,6 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard' import * as ListCard from '#/components/ListCard'
import {ContentHider} from '#/components/moderation/ContentHider' import {ContentHider} from '#/components/moderation/ContentHider'
import {toLex} from '#/types/bsky'
import {type EmbedType} from '#/types/bsky/post' import {type EmbedType} from '#/types/bsky/post'
import {type CommonProps} from './types' import {type CommonProps} from './types'
@@ -31,8 +30,7 @@ export function ModeratedListEmbed({
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const moderation = useMemo(() => { const moderation = useMemo(() => {
return moderationOpts return moderationOpts
? // TODO(phase4): drop toLex once list embed view emits #/lexicons views ? moderateUserList(embed.view, moderationOpts)
moderateUserList(toLex(embed.view), moderationOpts)
: undefined : undefined
}, [embed.view, moderationOpts]) }, [embed.view, moderationOpts])
return ( return (
+1 -3
View File
@@ -268,9 +268,7 @@ export function QuoteEmbed({
[embed], [embed],
) )
const moderation = useMemo(() => { const moderation = useMemo(() => {
return moderationOpts return moderationOpts ? moderatePost(quote, moderationOpts) : undefined
? moderatePost(bsky.toLex(quote), moderationOpts)
: undefined
}, [quote, moderationOpts]) }, [quote, moderationOpts])
const t = useTheme() const t = useTheme()
@@ -22,7 +22,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type chat} from '#/lexicons' import {type chat} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function RecentChats({ export function RecentChats({
postUri, postUri,
@@ -121,8 +120,7 @@ function RecentChatItem({
const primaryProfile = useProfileShadow(primaryMember) const primaryProfile = useProfileShadow(primaryMember)
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views const moderation = moderateProfile(primaryProfile, moderationOpts)
const moderation = moderateProfile(toLex(primaryProfile), moderationOpts)
const name = const name =
convo.kind === 'group' convo.kind === 'group'
? convo.details.name ? convo.details.name
+6 -6
View File
@@ -44,7 +44,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type Metrics} from '#/analytics' import {type Metrics} from '#/analytics'
import {useActorStatus} from '#/features/liveNow' import {useActorStatus} from '#/features/liveNow'
import * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function Default({ export function Default({
profile, profile,
@@ -170,7 +170,7 @@ export function Avatar({
liveOverride?: boolean liveOverride?: boolean
size?: number size?: number
}) { }) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts) const moderation = moderateProfile(profile, moderationOpts)
const {isActive: live} = useActorStatus(profile) const {isActive: live} = useActorStatus(profile)
@@ -240,7 +240,7 @@ function InlineNameAndHandle({
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
const t = useTheme() const t = useTheme()
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts) const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName( const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'), moderation.ui('displayName'),
@@ -294,7 +294,7 @@ export function Name({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle> textStyle?: StyleProp<TextStyle>
}) { }) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts) const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName( const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'), moderation.ui('displayName'),
@@ -479,7 +479,7 @@ export function FollowButtonInner({
}: FollowButtonProps) { }: FollowButtonProps) {
const {t: l} = useLingui() const {t: l} = useLingui()
const profile = useProfileShadow(profileUnshadowed) const profile = useProfileShadow(profileUnshadowed)
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts) const moderation = moderateProfile(profile, moderationOpts)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
profile, profile,
logContext, logContext,
@@ -617,7 +617,7 @@ export function Labels({
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
const moderation = moderateProfile(bsky.toLex(profile), moderationOpts) const moderation = moderateProfile(profile, moderationOpts)
const modui = moderation.ui('profileList') const modui = moderation.ui('profileList')
const followedBy = profile.viewer?.followedBy const followedBy = profile.viewer?.followedBy
@@ -39,7 +39,6 @@ import {IS_WEB_TOUCH_DEVICE} from '#/env'
import {useActorStatus} from '#/features/liveNow' import {useActorStatus} from '#/features/liveNow'
import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog' import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {type ProfileHoverCardProps} from './types' import {type ProfileHoverCardProps} from './types'
const floatingMiddlewares = [ const floatingMiddlewares = [
@@ -424,8 +423,7 @@ function Inner({
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const moderation = useMemo( const moderation = useMemo(
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views () => moderateProfile(profile, moderationOpts),
() => moderateProfile(toLex(profile), moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
const [descriptionRT] = useRichText(profile.description ?? '') const [descriptionRT] = useRichText(profile.description ?? '')
@@ -26,7 +26,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
function WizardListCard({ function WizardListCard({
type, type,
@@ -140,10 +139,7 @@ export function WizardProfileCard({
const included = isTarget || state.profiles.some(p => p.did === profile.did) const included = isTarget || state.profiles.some(p => p.did === profile.did)
const disabled = const disabled =
isTarget || (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE) isTarget || (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar')
const moderationUi = moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)
const displayName = profile.displayName const displayName = profile.displayName
? sanitizeDisplayName(profile.displayName) ? sanitizeDisplayName(profile.displayName)
: `@${sanitizeHandle(profile.handle)}` : `@${sanitizeHandle(profile.handle)}`
@@ -194,11 +190,9 @@ export function WizardFeedCard({
const isDiscover = generator.uri === DISCOVER_FEED_URI const isDiscover = generator.uri === DISCOVER_FEED_URI
const included = isDiscover || state.feeds.some(f => f.uri === generator.uri) const included = isDiscover || state.feeds.some(f => f.uri === generator.uri)
const disabled = isDiscover || (!included && state.feeds.length >= 3) const disabled = isDiscover || (!included && state.feeds.length >= 3)
// TODO(phase4): drop toLex once GeneratorView prop emits #/lexicons views const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui(
const moderationUi = moderateFeedGenerator( 'avatar',
toLex(generator), )
moderationOpts,
).ui('avatar')
const onPress = () => { const onPress = () => {
if (disabled) return if (disabled) return
@@ -33,7 +33,6 @@ import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {AvatarBubbles} from '../AvatarBubbles' import {AvatarBubbles} from '../AvatarBubbles'
import {Error} from '../Error' import {Error} from '../Error'
import {ProfileBadges} from '../ProfileBadges' import {ProfileBadges} from '../ProfileBadges'
@@ -411,8 +410,7 @@ function DefaultProfileCard({
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const enabled = canBeMessaged(profile) const enabled = canBeMessaged(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const handle = sanitizeHandle(profile.handle, '@') const handle = sanitizeHandle(profile.handle, '@')
const displayName = createSanitizedDisplayName( const displayName = createSanitizedDisplayName(
profile, profile,
@@ -488,8 +486,7 @@ function ExistingChatCard({
: createSanitizedDisplayName( : createSanitizedDisplayName(
convo.primaryMember, convo.primaryMember,
true, true,
// TODO(phase4): drop toLex once convo primaryMember emits #/lexicons views moderateProfile(convo.primaryMember, moderationOpts).ui(
moderateProfile(toLex(convo.primaryMember), moderationOpts).ui(
'displayName', 'displayName',
), ),
) )
@@ -28,7 +28,6 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
const DISPLAY_NAME_MAX_GRAPHEMES = 64 const DISPLAY_NAME_MAX_GRAPHEMES = 64
const DESCRIPTION_MAX_GRAPHEMES = 300 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 // We want to be working with a blank state here, so let's get the
// serialized version and turn it back into a RichText // serialized version and turn it back into a RichText
// TODO(phase4): drop toLex once the list view producer emits #/lexicons facets
const serialized = richTextToString( const serialized = richTextToString(
new RichTextAPI({text, facets: toLex(facets)}), new RichTextAPI({text, facets: facets}),
false, 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 * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type Props = { type Props = {
testID?: string testID?: string
@@ -90,8 +89,7 @@ function Tab({
const {t: l} = useLingui() const {t: l} = useLingui()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts!)
const moderation = moderateProfile(toLex(profile), moderationOpts!)
const displayName = sanitizeDisplayName( const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'), moderation.ui('displayName'),
+1 -3
View File
@@ -54,7 +54,6 @@ import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
type NewGroupChatItem = { type NewGroupChatItem = {
type: 'newGroupChat' type: 'newGroupChat'
@@ -1071,8 +1070,7 @@ function DefaultProfileCard({
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const enabled = canBeMessaged(profile) const enabled = canBeMessaged(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const handle = sanitizeHandle(profile.handle, '@') const handle = sanitizeHandle(profile.handle, '@')
const displayName = sanitizeDisplayName( const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
+1 -3
View File
@@ -29,7 +29,6 @@ import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type chat} from '#/lexicons' import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker' import {EmojiReactionPicker} from './EmojiReactionPicker'
import {canReact, hasReachedReactionLimit} from './util' import {canReact, hasReachedReactionLimit} from './util'
@@ -87,8 +86,7 @@ export let MessageContextMenu = ({
const str = richTextToString( const str = richTextToString(
new RichText({ new RichText({
text: message.text, text: message.text,
// TODO(phase4): drop toLex once the message producer emits #/lexicons facets facets: message.facets,
facets: toLex(message.facets),
}), }),
true, true,
) )
+1 -3
View File
@@ -344,9 +344,7 @@ let MessageItem = ({
size={AVATAR_SIZE} size={AVATAR_SIZE}
type={profile.associated?.labeler ? 'labeler' : 'user'} type={profile.associated?.labeler ? 'labeler' : 'user'}
onBeforePress={() => unstableCacheProfileView(queryClient, profile)} onBeforePress={() => unstableCacheProfileView(queryClient, profile)}
moderation={moderateProfile(bsky.toLex(profile), moderationOpts).ui( moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
'avatar',
)}
/> />
) : ( ) : (
<ProfileCard.AvatarPlaceholder size={AVATAR_SIZE} /> <ProfileCard.AvatarPlaceholder size={AVATAR_SIZE} />
+1 -3
View File
@@ -21,7 +21,6 @@ import {Link} from '#/components/Link'
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_LIQUID_GLASS} from '#/env' import {IS_LIQUID_GLASS} from '#/env'
import {toLex} from '#/types/bsky'
import {type ConvoWithDetails} from './util' import {type ConvoWithDetails} from './util'
const PFP_SIZE = 40 const PFP_SIZE = 40
@@ -86,8 +85,7 @@ function ProfileHeaderReady({
const {t: l} = useLingui() const {t: l} = useLingui()
const profile = useProfileShadow(convo.primaryMember) const profile = useProfileShadow(convo.primaryMember)
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const blockInfo = useMemo(() => { const blockInfo = useMemo(() => {
const modui = moderation.ui('profileView') const modui = moderation.ui('profileView')
@@ -10,7 +10,6 @@ import * as Toggle from '#/components/forms/Toggle'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function GroupChatProfileCard({ export function GroupChatProfileCard({
profile, profile,
@@ -21,8 +20,7 @@ export function GroupChatProfileCard({
}) { }) {
const t = useTheme() const t = useTheme()
const enabled = canBeAddedToGroup(profile) const enabled = canBeAddedToGroup(profile)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const handle = sanitizeHandle(profile.handle, '@') const handle = sanitizeHandle(profile.handle, '@')
const displayName = sanitizeDisplayName( const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
+1 -4
View File
@@ -154,10 +154,7 @@ export function canReact({
} }
if (primaryMember && moderationOpts) { if (primaryMember && moderationOpts) {
const moderation = moderateProfile( const moderation = moderateProfile(primaryMember, moderationOpts)
bsky.toLex(primaryMember),
moderationOpts,
)
if (convoState.convo.kind === 'direct') { if (convoState.convo.kind === 'direct') {
// Either direction (blocking or blocked-by) hides reactions in 1-1s // Either direction (blocking or blocked-by) hides reactions in 1-1s
if (moderation.blocked) return false if (moderation.blocked) return false
@@ -395,11 +395,9 @@ function GroupChatJoinDialogContent({code}: {code?: string}) {
{createSanitizedDisplayName( {createSanitizedDisplayName(
joinLinkPreview.owner, joinLinkPreview.owner,
true, true,
// TODO(phase4): drop toLex once join link preview emits #/lexicons views moderateProfile(joinLinkPreview.owner, moderationOpts).ui(
moderateProfile( 'displayName',
bsky.toLex(joinLinkPreview.owner), ),
moderationOpts,
).ui('displayName'),
)} )}
</InlineLinkText> </InlineLinkText>
</Trans> </Trans>
+1 -3
View File
@@ -16,7 +16,6 @@ import {
} from '#/components/moderation/LabelsOnMeDialog' } from '#/components/moderation/LabelsOnMeDialog'
import * as Pills from '#/components/Pills' import * as Pills from '#/components/Pills'
import {type app, type com} from '#/lexicons' import {type app, type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function PostAlerts({ export function PostAlerts({
post, post,
@@ -68,8 +67,7 @@ export function PostAlerts({
*/ */
const shownCauses = [...alerts, ...informs, ...modui.blurs] const shownCauses = [...alerts, ...informs, ...modui.blurs]
const additionalLabels = filterUserFacingLabels( const additionalLabels = filterUserFacingLabels(
// TODO(phase4): drop toLex once PostView labels are #/lexicons-typed allLabels,
toLex(allLabels),
currentAccount?.did, currentAccount?.did,
).filter(label => ).filter(label =>
shownCauses.every( shownCauses.every(
@@ -24,21 +24,7 @@ export function parseReportSubject(
} }
} }
/* const subject = rawSubject
* 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)
if ( if (
bsky.isType(app.bsky.actor.defs.profileViewBasic, subject) || bsky.isType(app.bsky.actor.defs.profileViewBasic, subject) ||
@@ -15,7 +15,6 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app, com, tools} from '#/lexicons' import {type app, com, tools} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function GoLiveDisabledDialog({ export function GoLiveDisabledDialog({
control, control,
@@ -60,7 +59,7 @@ export function DialogInner({
} else { } else {
await pdsClient.call( await pdsClient.call(
com.atproto.moderation.createReport, com.atproto.moderation.createReport,
toLex<com.atproto.moderation.createReport.$InputBody>({ {
reasonType: tools.ozone.report.defs.reasonAppeal.value, reasonType: tools.ozone.report.defs.reasonAppeal.value,
subject: { subject: {
$type: 'com.atproto.repo.strongRef', $type: 'com.atproto.repo.strongRef',
@@ -68,7 +67,7 @@ export function DialogInner({
cid: status.cid, cid: status.cid,
}, },
reason: details, reason: details,
}), },
{ {
service: api.moderation.service, service: api.moderation.service,
}, },
@@ -27,7 +27,7 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator' import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function LiveStatusDialog({ export function LiveStatusDialog({
control, control,
@@ -115,8 +115,7 @@ export function LiveStatus({
const dialogContext = Dialog.useDialogContext() const dialogContext = Dialog.useDialogContext()
const moderation = useMemo(() => { const moderation = useMemo(() => {
if (!moderationOpts) return undefined if (!moderationOpts) return undefined
// TODO(phase4): drop toLex once profile producers emit #/lexicons views return moderateStatus(profile, moderationOpts)
return moderateStatus(bsky.toLex(profile), moderationOpts)
}, [profile, moderationOpts]) }, [profile, moderationOpts])
return ( return (
+4 -11
View File
@@ -23,7 +23,7 @@ import {
useMaybeProfileShadow, useMaybeProfileShadow,
} from '#/state/cache/profile-shadow' } from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts' 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 {useTickEveryMinute} from '#/state/shell'
import {useDialogContext} from '#/components/Dialog' import {useDialogContext} from '#/components/Dialog'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
@@ -105,8 +105,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
const moderation = useMemo(() => { const moderation = useMemo(() => {
if (!actor || !('status' in actor && actor.status)) return undefined if (!actor || !('status' in actor && actor.status)) return undefined
if (!moderationOpts) return undefined if (!moderationOpts) return undefined
// TODO(phase4): drop toLex once profile producers emit #/lexicons views return moderateStatus(actor, moderationOpts)
return moderateStatus(bsky.toLex(actor), moderationOpts)
}, [actor, moderationOpts]) }, [actor, moderationOpts])
return useMemo(() => { return useMemo(() => {
@@ -120,12 +119,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
} }
if (shadowed && 'status' in shadowed && shadowed.status) { if (shadowed && 'status' in shadowed && shadowed.status) {
/* const status = 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 isValid = isStatusValidForViewers(status, config) const isValid = isStatusValidForViewers(status, config)
const isDisabled = status.isDisabled || false const isDisabled = status.isDisabled || false
const isActive = isStatusStillActive(status.expiresAt) const isActive = isStatusStillActive(status.expiresAt)
@@ -195,7 +189,6 @@ export function useLiveLinkMetaQuery(url: string | null) {
const liveNowConfig = useLiveNowConfig() const liveNowConfig = useLiveNowConfig()
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent()
return useQuery({ return useQuery({
enabled: !!url, enabled: !!url,
queryKey: ['link-meta', 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 {DEMO_FEED} from '#/lib/demo'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {type FeedAPI, type FeedAPIResponse} from './types' import {type FeedAPI, type FeedAPIResponse} from './types'
export class DemoFeedAPI implements FeedAPI { export class DemoFeedAPI implements FeedAPI {
@@ -12,17 +11,13 @@ export class DemoFeedAPI implements FeedAPI {
this.client = client this.client = client
} }
// eslint-disable-next-line @typescript-eslint/require-await
async peekLatest(): Promise<app.bsky.feed.defs.FeedViewPost> { async peekLatest(): Promise<app.bsky.feed.defs.FeedViewPost> {
/* return DEMO_FEED.feed[0]
* 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])
} }
// eslint-disable-next-line @typescript-eslint/require-await
async fetch(): Promise<FeedAPIResponse> { 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 {getContentLanguages} from '#/state/preferences/languages'
import {type FeedParams} from '#/state/queries/post-feed' import {type FeedParams} from '#/state/queries/post-feed'
import {app} from '#/lexicons' import {app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {FeedTuner} from '../feed-manip' import {FeedTuner} from '../feed-manip'
import {type FeedTunerFn} from '../feed-manip' import {type FeedTunerFn} from '../feed-manip'
import { import {
@@ -264,13 +263,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
return { return {
success: true, success: true,
cursor: res.cursor, cursor: res.cursor,
/* feed: slices.map(slice => slice._feedPost),
* 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)),
} }
} }
} }
+8 -33
View File
@@ -91,16 +91,7 @@ export async function post(
for (let i = 0; i < thread.posts.length; i++) { for (let i = 0; i < thread.posts.length; i++) {
const draft = thread.posts[i] const draft = thread.posts[i]
/* const rtPromise = resolveRT(resolveClients.appview, draft.richtext)
* 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 embedPromise = resolveEmbed( const embedPromise = resolveEmbed(
clients, clients,
queryClient, queryClient,
@@ -138,17 +129,11 @@ export async function post(
langs, langs,
labels, 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({ writes.push({
$type: 'com.atproto.repo.applyWrites#create', $type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.post', collection: 'app.bsky.feed.post',
rkey: rkey, rkey: rkey,
value: bsky.toLex(record), value: record,
}) })
if (i === 0 && thread.threadgate.some(tg => tg.type !== 'everybody')) { if (i === 0 && thread.threadgate.some(tg => tg.type !== 'everybody')) {
@@ -156,14 +141,10 @@ export async function post(
$type: 'com.atproto.repo.applyWrites#create', $type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.threadgate', collection: 'app.bsky.feed.threadgate',
rkey: rkey, rkey: rkey,
value: bsky.toLex( value: createThreadgateRecord({
createThreadgateRecord({
post: uri, post: uri,
allow: threadgateAllowUISettingToAllowRecordValue( allow: threadgateAllowUISettingToAllowRecordValue(thread.threadgate),
thread.threadgate,
),
}), }),
),
}) })
} }
@@ -175,12 +156,12 @@ export async function post(
$type: 'com.atproto.repo.applyWrites#create', $type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.postgate', collection: 'app.bsky.feed.postgate',
rkey: rkey, rkey: rkey,
value: bsky.toLex({ value: {
...thread.postgate, ...thread.postgate,
$type: 'app.bsky.feed.postgate', $type: 'app.bsky.feed.postgate',
createdAt: now.toISOString(), createdAt: now.toISOString(),
post: uri, post: uri,
}), },
}) })
} }
@@ -426,7 +407,7 @@ async function resolveMedia(
* structural shape matches the lexicon blob field; the CID hasher handles * structural shape matches the lexicon blob field; the CID hasher handles
* both class instances and plain lex blobs (see computeCid). * both class instances and plain lex blobs (see computeCid).
*/ */
video: bsky.toLex(videoDraft.pendingPublish.blobRef), video: videoDraft.pendingPublish.blobRef,
alt: videoDraft.altText || undefined, alt: videoDraft.altText || undefined,
captions: captions.length === 0 ? undefined : captions, captions: captions.length === 0 ? undefined : captions,
aspectRatio, aspectRatio,
@@ -475,13 +456,7 @@ async function resolveMedia(
title: resolvedLink.title, title: resolvedLink.title,
description: resolvedLink.description, description: resolvedLink.description,
thumb: blob, thumb: blob,
/* associatedRefs: resolvedLink.associatedRefs,
* 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),
}, },
} }
} }
+3 -15
View File
@@ -23,7 +23,6 @@ import {
import {type ComposerImage} from '#/state/gallery' import {type ComposerImage} from '#/state/gallery'
import {createComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery'
import {type ChatInvitePreview} from '#/state/queries/join-links' import {type ChatInvitePreview} from '#/state/queries/join-links'
import {type SessionAgent} from '#/state/session'
import {type Gif} from '#/features/gifPicker/types' import {type Gif} from '#/features/gifPicker/types'
import {app, chat, com} from '#/lexicons' import {app, chat, com} from '#/lexicons'
import {createGIFDescription} from '../gif-alt-text' 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 * Clients the link resolver needs. `appview` serves the `app.bsky.*` feed/graph
* reads plus handle resolution; `chat` serves the group join-link previews. * 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 = { export type ResolveClients = {
appview: Client appview: Client
chat: Client chat: Client
agent: SessionAgent
} }
type ResolvedExternalLink = { type ResolvedExternalLink = {
@@ -195,7 +191,7 @@ export async function resolveLink(
view: res.starterPack, view: res.starterPack,
} }
} }
return resolveExternal(clients, uri) return resolveExternal(uri)
// Forked from useGetPost. TODO: move into RQ. // Forked from useGetPost. TODO: move into RQ.
async function getPost({uri}: {uri: string}) { 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 return dotIndex > 0 ? filename.slice(0, dotIndex) : undefined
} }
async function resolveExternal( async function resolveExternal(uri: string): Promise<ResolvedExternalLink> {
clients: ResolveClients, const result = await getLinkMeta(uri)
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)
return { return {
type: 'external', type: 'external',
uri: result.url, uri: result.url,
+1 -5
View File
@@ -1,7 +1,6 @@
import {LINK_META_PROXY} from '#/lib/constants' import {LINK_META_PROXY} from '#/lib/constants'
import {getGiphyMetaUri} from '#/lib/strings/embed-player' import {getGiphyMetaUri} from '#/lib/strings/embed-player'
import {parseStarterPackUri} from '#/lib/strings/starter-pack' import {parseStarterPackUri} from '#/lib/strings/starter-pack'
import {type SessionAgent} from '#/state/session'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {isBskyAppUrl} from '../strings/url-helpers' import {isBskyAppUrl} from '../strings/url-helpers'
@@ -31,7 +30,6 @@ export interface LinkMeta {
} }
export async function getLinkMeta( export async function getLinkMeta(
agent: SessionAgent,
url: string, url: string,
timeout = 15e3, timeout = 15e3,
): Promise<LinkMeta> { ): Promise<LinkMeta> {
@@ -80,9 +78,7 @@ export async function getLinkMeta(
try { try {
const response = await fetch( const response = await fetch(
`${LINK_META_PROXY(agent.serviceUrl.toString() || '')}${encodeURIComponent( `${LINK_META_PROXY('')}${encodeURIComponent(url)}`,
url,
)}`,
{signal: controller.signal}, {signal: controller.signal},
) )
+2 -15
View File
@@ -12,7 +12,6 @@ import {
} from '#/lib/moderation/useGlobalLabelStrings' } from '#/lib/moderation/useGlobalLabelStrings'
import {useLabelDefinitions} from '#/state/preferences' import {useLabelDefinitions} from '#/state/preferences'
import {type app, type com} from '#/lexicons' import {type app, type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
export interface LabelInfo { export interface LabelInfo {
label: com.atproto.label.defs.Label label: com.atproto.label.defs.Label
@@ -23,26 +22,14 @@ export interface LabelInfo {
export function useLabelInfo(label: com.atproto.label.defs.Label): LabelInfo { export function useLabelInfo(label: com.atproto.label.defs.Label): LabelInfo {
const {i18n} = useLingui() 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 {labelDefs, labelers} = useLabelDefinitions()
const def = getDefinition( const def = getDefinition(labelDefs, label)
toLex<Record<string, InterpretedLabelValueDefinition[]>>(labelDefs),
label,
)
const globalLabelStrings = useGlobalLabelStrings() const globalLabelStrings = useGlobalLabelStrings()
return { return {
label, label,
def, def,
strings: getLabelStrings(i18n.locale, globalLabelStrings, def), strings: getLabelStrings(i18n.locale, globalLabelStrings, def),
labeler: toLex<app.bsky.labeler.defs.LabelerViewDetailed[]>(labelers).find( labeler: labelers.find(labeler => label.src === labeler.creator.did),
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 {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
import {type AppModerationCause} from '#/components/Pills' import {type AppModerationCause} from '#/components/Pills'
import {type com} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {useGlobalLabelStrings} from './useGlobalLabelStrings' import {useGlobalLabelStrings} from './useGlobalLabelStrings'
import {getDefinition, getLabelStrings} from './useLabelInfo' import {getDefinition, getLabelStrings} from './useLabelInfo'
@@ -129,18 +127,7 @@ export function useModerationCauseDescription(
} }
} }
if (cause.type === 'label') { if (cause.type === 'label') {
/* const def = cause.labelDef || getDefinition(labelDefs, cause.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 strings = getLabelStrings(i18n.locale, globalLabelStrings, def) const strings = getLabelStrings(i18n.locale, globalLabelStrings, def)
const labeler = labelers.find(l => l.creator.did === cause.label.src) const labeler = labelers.find(l => l.creator.did === cause.label.src)
let source = labeler let source = labeler
+2 -3
View File
@@ -46,7 +46,7 @@ import {Error} from '#/components/Error'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {IS_LIQUID_GLASS} from '#/env' import {IS_LIQUID_GLASS} from '#/env'
import {chat} from '#/lexicons' import {chat} from '#/lexicons'
import {isType, toLex} from '#/types/bsky' import {isType} from '#/types/bsky'
import {ChatDisabled} from './components/ChatDisabled' import {ChatDisabled} from './components/ChatDisabled'
import {ChatEnded} from './components/ChatEnded' import {ChatEnded} from './components/ChatEnded'
import {ChatLocked} from './components/ChatLocked' import {ChatLocked} from './components/ChatLocked'
@@ -228,8 +228,7 @@ function InnerReady({
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const primaryMemberModeration = useMemo(() => { const primaryMemberModeration = useMemo(() => {
if (!primaryMember || !moderationOpts) return null if (!primaryMember || !moderationOpts) return null
// TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views return moderateProfile(primaryMember, moderationOpts)
return moderateProfile(toLex(primaryMember), moderationOpts)
}, [primaryMember, moderationOpts]) }, [primaryMember, moderationOpts])
const header = <MessagesListHeader convo={convo} /> const header = <MessagesListHeader convo={convo} />
@@ -21,7 +21,6 @@ import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
import {MemberMenu} from './MemberMenu' import {MemberMenu} from './MemberMenu'
import {RemoveMemberPrompt} from './prompts' import {RemoveMemberPrompt} from './prompts'
import {StatusBadge} from './StatusBadge' import {StatusBadge} from './StatusBadge'
@@ -81,8 +80,7 @@ export function Member({
return <MemberPlaceholder /> return <MemberPlaceholder />
} }
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const isDeletedAccount = profile.handle === 'missing.invalid' const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount const displayName = isDeletedAccount
@@ -108,13 +106,10 @@ export function Member({
} }
const joinedReason = profile.kind?.addedBy 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, profile.kind.addedBy,
true, true,
moderateProfile(toLex(profile.kind.addedBy), moderationOpts).ui( moderateProfile(profile.kind.addedBy, moderationOpts).ui('displayName'),
'displayName',
),
)}` )}`
: l`Added by invite link` : l`Added by invite link`
+4 -7
View File
@@ -187,9 +187,8 @@ export function JoinRequest({setScreenState}: Props) {
{createSanitizedDisplayName( {createSanitizedDisplayName(
joinLinkPreview.owner, joinLinkPreview.owner,
true, true,
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
moderateProfile( moderateProfile(
bsky.toLex(joinLinkPreview.owner), joinLinkPreview.owner,
moderationOpts, moderationOpts,
).ui('displayName'), ).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 request access to this group chat.`
: l`Sign in to accept invite.`}{' '} : l`Sign in to accept invite.`}{' '}
{joinLinkPreview.joinRule === 'followedByOwner' && {joinLinkPreview.joinRule === 'followedByOwner' &&
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
l`Only people ${createSanitizedDisplayName( l`Only people ${createSanitizedDisplayName(
joinLinkPreview.owner, joinLinkPreview.owner,
true, true,
moderateProfile( moderateProfile(joinLinkPreview.owner, moderationOpts).ui(
bsky.toLex(joinLinkPreview.owner), 'displayName',
moderationOpts, ),
).ui('displayName'),
)} follows can join.`} )} follows can join.`}
</Text> </Text>
<ActionButtons setScreenState={setScreenState} /> <ActionButtons setScreenState={setScreenState} />
@@ -125,8 +125,7 @@ function DirectChatItem({
const {isWithinLeftPanel} = useIsWithinSplitView() const {isWithinLeftPanel} = useIsWithinSplitView()
const moderation = useMemo( const moderation = useMemo(
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views () => moderateProfile(profile, moderationOpts),
() => moderateProfile(bsky.toLex(profile), moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
@@ -193,10 +192,7 @@ function GroupChatItem({
const moderation = useMemo( const moderation = useMemo(
() => () =>
groupOwner groupOwner ? moderateProfile(groupOwner, moderationOpts) : undefined,
? // TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views
moderateProfile(bsky.toLex(groupOwner), moderationOpts)
: undefined,
[groupOwner, moderationOpts], [groupOwner, moderationOpts],
) )
@@ -423,15 +419,7 @@ function BaseChatItem({
for (const member of convo.view.members) { for (const member of convo.view.members) {
unstableCacheProfileView(queryClient, member) unstableCacheProfileView(queryClient, member)
} }
/* precacheConvoQuery(queryClient, convo.view)
* `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),
)
void decrementBadgeCount(convo.view.unreadCount) void decrementBadgeCount(convo.view.unreadCount)
if (isDeletedAccount) { if (isDeletedAccount) {
e.preventDefault() e.preventDefault()
@@ -525,10 +513,7 @@ function BaseChatItem({
} }
onPressIn={() => onPressIn={() =>
// see onPress: old-typed dms/util view into the new-typed cache // see onPress: old-typed dms/util view into the new-typed cache
precacheConvoQuery( precacheConvoQuery(queryClient, convo.view)
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>(convo.view),
)
} }
onPress={onPress} onPress={onPress}
onLongPress={showMenu && IS_NATIVE ? onLongPress : undefined} onLongPress={showMenu && IS_NATIVE ? onLongPress : undefined}
@@ -17,7 +17,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {usePromptControl} from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons' import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) { export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
@@ -115,8 +114,7 @@ function InviterHeader({
const t = useTheme() const t = useTheme()
const profile = useProfileShadow(profileUnshadowed) const profile = useProfileShadow(profileUnshadowed)
const moderation = useMemo( const moderation = useMemo(
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views () => moderateProfile(profile, moderationOpts),
() => moderateProfile(toLex(profile), moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
const displayName = createSanitizedDisplayName( const displayName = createSanitizedDisplayName(
@@ -34,7 +34,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type chat} from '#/lexicons' import {type chat} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {CopyTextButton} from './CopyTextButton' import {CopyTextButton} from './CopyTextButton'
import {EditTextButton} from './EditTextButton' import {EditTextButton} from './EditTextButton'
@@ -69,8 +68,7 @@ export function InviteLinkDialog({
const ownerName = createSanitizedDisplayName( const ownerName = createSanitizedDisplayName(
owner, owner,
false, false,
// TODO(phase4): drop toLex once GroupConvoMember emits #/lexicons views moderateProfile(owner, moderationOpts).ui('displayName'),
moderateProfile(toLex(owner), moderationOpts).ui('displayName'),
) )
const {joinLink} = convo.details const {joinLink} = convo.details
@@ -38,7 +38,6 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
import {type chat} from '#/lexicons' import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {type MessageEmbedState} from './MessageInputEmbed' import {type MessageEmbedState} from './MessageInputEmbed'
const MIN_HEIGHT = 40 const MIN_HEIGHT = 40
@@ -70,15 +69,7 @@ export function MessageComposer({
const {getDraft, clearDraft} = useMessageDraft() const {getDraft, clearDraft} = useMessageDraft()
const composerInternalApiRef = useComposerInternalApiRef() const composerInternalApiRef = useComposerInternalApiRef()
const {replyTo: replyToOld, clearReply} = useMessageReplies() const {replyTo: replyToOld, clearReply} = useMessageReplies()
/* const replyTo = replyToOld
* `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 [text, setText] = useState(getDraft) const [text, setText] = useState(getDraft)
useSaveMessageDraft(text) useSaveMessageDraft(text)
@@ -569,24 +569,18 @@ export function MessagesList({
try { try {
const post = await getPost({uri: embedState.uri}) const post = await getPost({uri: embedState.uri})
if (post) { if (post) {
/* embed = {
* `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>>({
$type: 'app.bsky.embed.record', $type: 'app.bsky.embed.record',
record: { record: {
uri: post.uri, uri: post.uri,
cid: post.cid, cid: post.cid,
}, },
}) }
embedView = bsky.toLex<$Typed<app.bsky.embed.record.View>>({ embedView = {
$type: 'app.bsky.embed.record#view', $type: 'app.bsky.embed.record#view',
record: createEmbedViewRecordFromPost(post), record: createEmbedViewRecordFromPost(post),
}) }
stripLinkFacet(uri => { stripLinkFacet(uri => {
if (!isBskyPostUrl(uri)) return false if (!isBskyPostUrl(uri)) return false
@@ -611,14 +605,10 @@ export function MessagesList({
const joinLinkPreview = await getJoinLinkPreview({code, hasSession}) const joinLinkPreview = await getJoinLinkPreview({code, hasSession})
if (joinLinkPreview) { if (joinLinkPreview) {
/* embedView = {
* 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>>({
$type: 'chat.bsky.embed.joinLink#view', $type: 'chat.bsky.embed.joinLink#view',
joinLinkPreview, joinLinkPreview,
}) }
} }
stripLinkFacet(uri => getChatInviteCodeFromUrl(uri) === code) 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 {ProfileBadges} from '#/components/ProfileBadges'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
export function MessagesListInfoPanel({ export function MessagesListInfoPanel({
convo, convo,
@@ -35,11 +34,10 @@ export function MessagesListInfoPanel({
)[0] )[0]
const handle = sanitizeHandle(profile.handle, '@') const handle = sanitizeHandle(profile.handle, '@')
const displayName = moderationOpts const displayName = moderationOpts
? // TODO(phase4): drop toLex once convo members emit #/lexicons views ? createSanitizedDisplayName(
createSanitizedDisplayName(
profile, profile,
true, true,
moderateProfile(toLex(profile), moderationOpts).ui('displayName'), moderateProfile(profile, moderationOpts).ui('displayName'),
) )
: handle : handle
const profileLink = const profileLink =
@@ -41,7 +41,6 @@ import * as Menu from '#/components/Menu'
import {ReportDialog} from '#/components/moderation/ReportDialog' import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {type chat} from '#/lexicons' import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function RejectMenu({ export function RejectMenu({
convo, convo,
@@ -229,18 +228,10 @@ export function AcceptChatButton({
onMutate: () => { onMutate: () => {
onAcceptConvo?.() onAcceptConvo?.()
if (currentScreen === 'list') { if (currentScreen === 'list') {
/* precacheConvoQuery(queryClient, {
* `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, ...convo,
status: 'accepted', status: 'accepted',
}), })
)
navigation.navigate('MessagesConversation', { navigation.navigate('MessagesConversation', {
conversation: convo.id, conversation: convo.id,
accept: true, accept: true,
@@ -16,7 +16,6 @@ import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
const AVI_SIZE = 20 const AVI_SIZE = 20
@@ -94,8 +93,7 @@ export function KnownLikers({post}: {post: AppBskyFeedDefs.PostView}) {
.map(like => like.actor) .map(like => like.actor)
.map(actor => ({ .map(actor => ({
actor, actor,
// TODO(phase4): drop toLex once useLikedBySampleQuery emits #/lexicons views moderation: moderateProfile(actor, moderationOpts),
moderation: moderateProfile(toLex(actor), moderationOpts),
})) }))
.filter(({actor, moderation}) => { .filter(({actor, moderation}) => {
const modui = moderation.ui('profileList') const modui = moderation.ui('profileList')
@@ -33,7 +33,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {ProfileHeaderDisplayName} from './DisplayName' import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog' import {EditProfileDialog} from './EditProfileDialog'
import {ProfileHeaderHandle} from './Handle' import {ProfileHeaderHandle} from './Handle'
@@ -67,7 +66,7 @@ let ProfileHeaderLabeler = ({
const isSelf = currentAccount?.did === profile.did const isSelf = currentAccount?.did === profile.did
const moderation = useMemo( const moderation = useMemo(
() => moderateProfile(toLex(profile), moderationOpts), () => moderateProfile(profile, moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
@@ -40,7 +40,6 @@ import {IS_IOS, IS_NATIVE} from '#/env'
import {InviteFriendsDialog} from '#/features/inviteFriends' import {InviteFriendsDialog} from '#/features/inviteFriends'
import {useActorStatus} from '#/features/liveNow' import {useActorStatus} from '#/features/liveNow'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {GermButton} from '../components/GermButton' import {GermButton} from '../components/GermButton'
import {ProfileHeaderDisplayName} from './DisplayName' import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog' import {EditProfileDialog} from './EditProfileDialog'
@@ -69,7 +68,7 @@ let ProfileHeaderStandard = ({
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {_} = useLingui() const {_} = useLingui()
const moderation = useMemo( const moderation = useMemo(
() => moderateProfile(toLex(profile), moderationOpts), () => moderateProfile(profile, moderationOpts),
[profile, moderationOpts], [profile, moderationOpts],
) )
const [, queueUnblock] = useProfileBlockMutationQueue(profile) const [, queueUnblock] = useProfileBlockMutationQueue(profile)
+1 -3
View File
@@ -22,7 +22,6 @@ import {Header} from '#/components/Layout'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import { import {
HeaderLabelerButtons, HeaderLabelerButtons,
ProfileHeaderLabeler, ProfileHeaderLabeler,
@@ -112,8 +111,7 @@ const MinimalHeader = memo(function MinimalHeader({
const profile = useProfileShadow(profileUnshadowed) const profile = useProfileShadow(profileUnshadowed)
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const moderation = useMemo( const moderation = useMemo(
() => () => (moderationOpts ? moderateProfile(profile, moderationOpts) : null),
moderationOpts ? moderateProfile(toLex(profile), moderationOpts) : null,
[moderationOpts, profile], [moderationOpts, profile],
) )
const [visible, setVisible] = useState(false) const [visible, setVisible] = useState(false)
@@ -24,7 +24,6 @@ import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {MoreOptionsMenu} from './MoreOptionsMenu' import {MoreOptionsMenu} from './MoreOptionsMenu'
import {SubscribeMenu} from './SubscribeMenu' import {SubscribeMenu} from './SubscribeMenu'
@@ -130,8 +129,7 @@ export function Header({
list.description list.description
? new RichTextAPI({ ? new RichTextAPI({
text: list.description, text: list.description,
// TODO(phase4): drop toLex once the list view producer emits #/lexicons facets facets: list.descriptionFacets,
facets: toLex(list.descriptionFacets),
}) })
: undefined, : undefined,
[list], [list],
+1 -3
View File
@@ -39,7 +39,6 @@ import {Loader} from '#/components/Loader'
import * as Hider from '#/components/moderation/Hider' import * as Hider from '#/components/moderation/Hider'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky'
import {AboutSection} from './AboutSection' import {AboutSection} from './AboutSection'
import {ErrorScreen} from './components/ErrorScreen' import {ErrorScreen} from './components/ErrorScreen'
import {Header} from './components/Header' import {Header} from './components/Header'
@@ -167,8 +166,7 @@ function ProfileListScreenLoaded({
const [headerHeight, setHeaderHeight] = useState<number | null>(null) const [headerHeight, setHeaderHeight] = useState<number | null>(null)
const moderation = useMemo(() => { const moderation = useMemo(() => {
// TODO(phase4): drop toLex once ListView prop emits #/lexicons views return moderateUserList(list, moderationOpts)
return moderateUserList(toLex(list), moderationOpts)
}, [list, moderationOpts]) }, [list, moderationOpts])
useSetTitle(isHidden ? _(msg`List Hidden`) : list.name) useSetTitle(isHidden ? _(msg`List Hidden`) : list.name)
@@ -22,7 +22,6 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function SearchHistory({ export function SearchHistory({
searchHistory, searchHistory,
@@ -207,8 +206,7 @@ function RecentProfileItem({
const {t: l} = useLingui() const {t: l} = useLingui()
const width = 80 const width = 80
// TODO(phase4): drop toLex once profile prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const name = sanitizeDisplayName( const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'), moderation.ui('displayName'),
@@ -29,7 +29,6 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import * as ModuleHeader from '../components/ModuleHeader' import * as ModuleHeader from '../components/ModuleHeader'
import {toLex} from '#/types/bsky'
const IMAGE_SIZE = 56 const IMAGE_SIZE = 56
@@ -307,8 +306,7 @@ function useModerateTrendingActors(
return actors return actors
.filter(actor => { .filter(actor => {
// TODO(phase4): drop toLex once TrendView actors emit #/lexicons views const decision = moderateProfile(actor, moderationOpts)
const decision = moderateProfile(toLex(actor), moderationOpts)
return !decision.ui('avatar').filter && !decision.ui('avatar').blur return !decision.ui('avatar').filter && !decision.ui('avatar').blur
}) })
.slice(0, 3) .slice(0, 3)
+2 -7
View File
@@ -70,7 +70,6 @@ import {useActorStatus} from '#/features/liveNow'
import {type app} from '#/lexicons' import {type app} from '#/lexicons'
import {device, useStorage} from '#/storage' import {device, useStorage} from '#/storage'
import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged'
import {toLex} from '#/types/bsky'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export function SettingsScreen({}: Props) { export function SettingsScreen({}: Props) {
@@ -331,8 +330,7 @@ function ProfilePreview({
if (!moderationOpts) return null if (!moderationOpts) return null
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views const moderation = moderateProfile(profile, moderationOpts)
const moderation = moderateProfile(toLex(profile), moderationOpts)
const displayName = sanitizeDisplayName( const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle), profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'), moderation.ui('displayName'),
@@ -639,10 +637,7 @@ function AccountRow({
<UserAvatar <UserAvatar
size={28} size={28}
avatar={profile.avatar} avatar={profile.avatar}
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
moderation={moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)}
type={profile.associated?.labeler ? 'labeler' : 'user'} type={profile.associated?.labeler ? 'labeler' : 'user'}
live={live} live={live}
hideLiveBadge hideLiveBadge
@@ -411,8 +411,7 @@ function Header({
const richText = record.description const richText = record.description
? new RichTextAPI({ ? new RichTextAPI({
text: record.description, text: record.description,
// TODO(phase4): drop toLex once the starterpack record producer emits #/lexicons facets facets: record.descriptionFacets,
facets: bsky.toLex(record.descriptionFacets),
}) })
: undefined : undefined
+1 -2
View File
@@ -808,8 +808,7 @@ function Overlay({
: undefined : undefined
const richText = new RichTextAPI({ const richText = new RichTextAPI({
text: record?.text || '', text: record?.text || '',
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets facets: record?.facets,
facets: bsky.toLex(record?.facets),
}) })
const handle = sanitizeHandle(post.author.handle, '@') const handle = sanitizeHandle(post.author.handle, '@')
+4 -15
View File
@@ -205,12 +205,8 @@ export function useFeedPreviews(
if (item.isFallbackMarker) continue if (item.isFallbackMarker) continue
const moderations = item.items.map(item => const moderations = item.items.map(item =>
// TODO(phase4): drop toLex once feed-manip is migrated off
// @atproto/api and yields lex-typed slice items. // @atproto/api and yields lex-typed slice items.
moderatePost( moderatePost(item.post, moderationOpts!),
bsky.toLex<app.bsky.feed.defs.PostView>(item.post),
moderationOpts!,
),
) )
// apply moderation filters // apply moderation filters
@@ -236,19 +232,12 @@ export function useFeedPreviews(
const feedPostSliceItem: FeedPostSliceItem = { const feedPostSliceItem: FeedPostSliceItem = {
_reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`, _reactKey: `${item._reactKey}-${i}-${subItem.post.uri}`,
uri: 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. // off @atproto/api and yields lex-typed slice items.
post: bsky.toLex<app.bsky.feed.defs.PostView>( post: subItem.post,
subItem.post, record: subItem.record,
),
record: bsky.toLex<app.bsky.feed.post.Main>(
subItem.record,
),
moderation: moderations[i], moderation: moderations[i],
parentAuthor: subItem.parentAuthor parentAuthor: subItem.parentAuthor
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>( ? subItem.parentAuthor
subItem.parentAuthor,
)
: undefined, : undefined,
isParentBlocked: subItem.isParentBlocked, isParentBlocked: subItem.isParentBlocked,
isParentNotFound: subItem.isParentNotFound, isParentNotFound: subItem.isParentNotFound,
@@ -57,24 +57,16 @@ export function useAddGroupMembers(
chat.bsky.actor.defs.ProfileViewBasic[] chat.bsky.actor.defs.ProfileViewBasic[]
>(listConvoMembersQueryKey(convoId)) >(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 = const addedBy: chat.bsky.actor.defs.ProfileViewBasic | undefined =
myProfile myProfile
? bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({ ? {
...myProfile, ...myProfile,
$type: 'chat.bsky.actor.defs#profileViewBasic', $type: 'chat.bsky.actor.defs#profileViewBasic',
}) }
: undefined : undefined
const optimisticMembers: chat.bsky.actor.defs.ProfileViewBasic[] = const optimisticMembers: chat.bsky.actor.defs.ProfileViewBasic[] =
profiles.map(profile => profiles.map(profile => ({
bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic>({
...profile, ...profile,
$type: 'chat.bsky.actor.defs#profileViewBasic', $type: 'chat.bsky.actor.defs#profileViewBasic',
kind: { kind: {
@@ -82,8 +74,7 @@ export function useAddGroupMembers(
role: 'standard', role: 'standard',
addedBy, addedBy,
}, },
}), }))
)
queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>( queryClient.setQueryData<chat.bsky.convo.defs.ConvoView>(
CONVO_KEY(convoId), CONVO_KEY(convoId),
+1 -4
View File
@@ -297,11 +297,8 @@ export function* findAllPostsInQueryData(
if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) { if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) {
const quotedPost = getEmbeddedPost(item.subject?.embed) const quotedPost = getEmbeddedPost(item.subject?.embed)
if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) {
// TODO(phase4): drop toLex once ../util is migrated and
// embedViewRecordToPostView returns the lexicon PostView. // embedViewRecordToPostView returns the lexicon PostView.
yield bsky.toLex<app.bsky.feed.defs.PostView>( yield embedViewRecordToPostView(quotedPost)
embedViewRecordToPostView(quotedPost),
)
} }
} }
} }
+4 -12
View File
@@ -288,9 +288,8 @@ export function usePostFeedQuery(
.map(slice => { .map(slice => {
const moderations = slice.items.map(item => const moderations = slice.items.map(item =>
moderatePost( moderatePost(
// TODO(phase4): drop toLex once feed-manip is migrated
// off @atproto/api and yields lex-typed slice items. // off @atproto/api and yields lex-typed slice items.
bsky.toLex<app.bsky.feed.defs.PostView>(item.post), item.post,
moderationOpts!, moderationOpts!,
), ),
) )
@@ -342,19 +341,12 @@ export function usePostFeedQuery(
const feedPostSliceItem: FeedPostSliceItem = { const feedPostSliceItem: FeedPostSliceItem = {
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`, _reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
uri: 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. // off @atproto/api and yields lex-typed slice items.
post: bsky.toLex<app.bsky.feed.defs.PostView>( post: item.post,
item.post, record: item.record,
),
record: bsky.toLex<app.bsky.feed.post.Main>(
item.record,
),
moderation: moderations[i], moderation: moderations[i],
parentAuthor: item.parentAuthor parentAuthor: item.parentAuthor
? bsky.toLex<app.bsky.actor.defs.ProfileViewBasic>( ? item.parentAuthor
item.parentAuthor,
)
: undefined, : undefined,
isParentBlocked: item.isParentBlocked, isParentBlocked: item.isParentBlocked,
isParentNotFound: item.isParentNotFound, isParentNotFound: item.isParentNotFound,
+2 -11
View File
@@ -44,7 +44,6 @@ import {useAgeAssurance} from '#/ageAssurance'
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons' import {app} from '#/lexicons'
import {toLex} from '#/types/bsky'
export * from '#/state/queries/preferences/const' export * from '#/state/queries/preferences/const'
export * from '#/state/queries/preferences/moderation' export * from '#/state/queries/preferences/moderation'
@@ -110,16 +109,8 @@ export function usePreferencesQuery() {
) { ) {
data = { data = {
...data, ...data,
/* moderationPrefs: makeAgeRestrictedModerationPrefs(
* TODO(phase4): drop the toLex bridges once data.moderationPrefs,
* `#/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)),
), ),
} }
} }
+4 -6
View File
@@ -7,7 +7,7 @@ import {
resolveLink, resolveLink,
} from '#/lib/api/resolve' } from '#/lib/api/resolve'
import {STALE} from '#/state/queries/index' 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' import {type Gif} from '#/features/gifPicker/types'
export const RQKEY_LINK_ROOT = 'resolve-link' 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 * Bundle the clients the link resolver needs from the session hooks. The
* appview client serves the `app.bsky.*` reads and handle resolution, the chat * appview client serves the `app.bsky.*` reads and handle resolution, and the
* client serves group join-link previews, and the bridge agent is still passed * chat client serves group join-link previews.
* through to the not-yet-migrated `getLinkMeta`.
*/ */
export function useResolveClients(): ResolveClients { export function useResolveClients(): ResolveClients {
const appview = useLexClient() const appview = useLexClient()
const chat = useChatClient() const chat = useChatClient()
const agent = useAgent() return {appview, chat}
return {appview, chat, agent}
} }
export function useResolveLinkQuery(url: string) { export function useResolveLinkQuery(url: string) {
+7 -19
View File
@@ -255,27 +255,23 @@ export function* getThreadPlaceholderCandidates(
for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) { for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(post) 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)) { for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post)) yield postViewToThreadPlaceholder(post)
} }
for (let post of findAllPostsInQuoteQueryData(queryClient, uri)) { for (let post of findAllPostsInQuoteQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post)) yield postViewToThreadPlaceholder(post)
} }
for (let post of findAllPostsInSearchQueryData(queryClient, uri)) { for (let post of findAllPostsInSearchQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post)) yield postViewToThreadPlaceholder(post)
} }
for (let post of findAllPostsInBookmarksQueryData(queryClient, uri)) { for (let post of findAllPostsInBookmarksQueryData(queryClient, uri)) {
yield postViewToThreadPlaceholder(bsky.toLex(post)) yield postViewToThreadPlaceholder(post)
} }
for (let post of findAllPostsInExploreFeedPreviewsQueryData( for (let post of findAllPostsInExploreFeedPreviewsQueryData(
queryClient, queryClient,
uri, uri,
)) { )) {
yield postViewToThreadPlaceholder(bsky.toLex(post)) yield postViewToThreadPlaceholder(post)
} }
} }
@@ -302,11 +298,7 @@ export function* findAllPostsInQueryData(
const qp = getEmbeddedPost(item.value.post.embed) const qp = getEmbeddedPost(item.value.post.embed)
if (qp && didOrHandleUriMatches(atUri, qp)) { if (qp && didOrHandleUriMatches(atUri, qp)) {
/* yield embedViewRecordToPostView(qp)
* TODO(phase4): drop toLex once `embedViewRecordToPostView` in
* `#/state/queries/util` is migrated to `#/lexicons` types.
*/
yield bsky.toLex(embedViewRecordToPostView(qp))
} }
} }
} }
@@ -335,11 +327,7 @@ export function* findAllProfilesInQueryData(
const qp = getEmbeddedPost(item.value.post.embed) const qp = getEmbeddedPost(item.value.post.embed)
if (qp && qp.author.did === did) { if (qp && qp.author.did === did) {
/* yield qp.author
* TODO(phase4): drop toLex once `getEmbeddedPost` in
* `#/state/queries/util` is migrated to `#/lexicons` types.
*/
yield bsky.toLex(qp.author)
} }
} }
} }
+25
View File
@@ -80,6 +80,7 @@ const ApiContext = createContext<SessionApiContext>({
resumeSession: async () => {}, resumeSession: async () => {},
removeAccount: () => {}, removeAccount: () => {},
partialRefreshSession: async () => {}, partialRefreshSession: async () => {},
refreshSession: () => Promise.resolve(undefined),
}) })
ApiContext.displayName = 'SessionApiContext' ApiContext.displayName = 'SessionApiContext'
@@ -344,6 +345,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}) })
}, [store, state, cancelPendingTask]) }, [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']>( const removeAccount = useCallback<SessionApiContext['removeAccount']>(
account => { account => {
addSessionDebugLog({ addSessionDebugLog({
@@ -443,6 +466,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
resumeSession, resumeSession,
removeAccount, removeAccount,
partialRefreshSession, partialRefreshSession,
refreshSession,
}), }),
[ [
createAccount, createAccount,
@@ -452,6 +476,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
resumeSession, resumeSession,
removeAccount, removeAccount,
partialRefreshSession, partialRefreshSession,
refreshSession,
], ],
) )
+11
View File
@@ -51,4 +51,15 @@ export type SessionApiContext = {
* `persistSessionHandler`. * `persistSessionHandler`.
*/ */
partialRefreshSession: () => Promise<void> 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 {shortenLinks} from '#/lib/strings/rich-text-manip'
import {type ComposerImage} from '#/state/gallery' import {type ComposerImage} from '#/state/gallery'
import {threadgateAllowUISettingToAllowRecordValue} from '#/state/queries/threadgate/util' import {threadgateAllowUISettingToAllowRecordValue} from '#/state/queries/threadgate/util'
import {createPublicAgent} from '#/state/session/agent'
import {getPublicLexClient} from '#/state/session/clients' import {getPublicLexClient} from '#/state/session/clients'
import { import {
type ComposerState, type ComposerState,
@@ -141,10 +140,9 @@ async function postDraftToServerPost(
// Add quote record embed // Add quote record embed
if (post.embed.quote) { if (post.embed.quote) {
const agent = createPublicAgent()
const publicClient = getPublicLexClient() const publicClient = getPublicLexClient()
const resolved = await resolveLink( const resolved = await resolveLink(
{appview: publicClient, chat: publicClient, agent}, {appview: publicClient, chat: publicClient},
post.embed.quote.uri, post.embed.quote.uri,
) )
if (resolved && resolved.type === 'record') { if (resolved && resolved.type === 'record') {
@@ -148,15 +148,12 @@ let NotificationFeedItem = ({
{ {
profile: item.notification.author, profile: item.notification.author,
href: makeProfileLink(item.notification.author), href: makeProfileLink(item.notification.author),
moderation: moderateProfile( moderation: moderateProfile(item.notification.author, moderationOpts),
bsky.toLex(item.notification.author),
moderationOpts,
),
}, },
...(item.additional?.map(({author}) => ({ ...(item.additional?.map(({author}) => ({
profile: author, profile: author,
href: makeProfileLink(author), href: makeProfileLink(author),
moderation: moderateProfile(bsky.toLex(author), moderationOpts), moderation: moderateProfile(author, moderationOpts),
})) || []), })) || []),
].filter( ].filter(
(author, index, arr) => (author, index, arr) =>
+1 -2
View File
@@ -73,8 +73,7 @@ export function PostQuotes({uri}: {uri: string}) {
) { ) {
return null return null
} }
// TODO(phase4): drop toLex once usePostQuotesQuery emits #/lexicons views const moderation = moderatePost(post, moderationOpts)
const moderation = moderatePost(bsky.toLex(post), moderationOpts)
return {post, record: post.record, moderation} return {post, record: post.record, moderation}
}), }),
) )
+3 -8
View File
@@ -61,17 +61,13 @@ export function Post({
record record
? new RichTextAPI({ ? new RichTextAPI({
text: record.text, text: record.text,
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets facets: record.facets,
facets: bsky.toLex(record.facets),
}) })
: undefined, : undefined,
[record], [record],
) )
const moderation = useMemo( const moderation = useMemo(
() => () => (moderationOpts ? moderatePost(post, moderationOpts) : undefined),
moderationOpts
? moderatePost(bsky.toLex(post), moderationOpts)
: undefined,
[moderationOpts, post], [moderationOpts, post],
) )
if (postShadowed === POST_TOMBSTONE) { if (postShadowed === POST_TOMBSTONE) {
@@ -135,8 +131,7 @@ function PostInner({
text: record.text, text: record.text,
author: post.author, author: post.author,
embed: post.embed, embed: post.embed,
// TODO(phase4): drop toLex once the composer state accepts SDK ModerationDecision moderation: moderation,
moderation: bsky.toLex(moderation),
langs: record.langs, langs: record.langs,
}, },
logContext: 'PostReply', logContext: 'PostReply',
+1 -2
View File
@@ -99,8 +99,7 @@ export function PostFeedItem({
() => () =>
new RichTextAPI({ new RichTextAPI({
text: record.text, text: record.text,
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets facets: record.facets,
facets: bsky.toLex(record.facets),
}), }),
[record], [record],
) )