collapse the dual-world type layer and delete the widening shims
The types/bsky post/profile/starterPack unions drop their @atproto/api arms, and dangerousIsType/validate go with the old-world guards they wrapped. The moderation subjects.ts widening shim and rich-text-helpers' asSdkFacets both existed only to bridge branded and unbranded views, so their 55 and 14 callers now go straight to @bsky.app/sdk/moderation and the raw facets. Boundary fallout: lexicon token defs are camelCase schema objects needing .value, and the branded string slots that the widening used to absorb are now cast or branded at their producers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {scheduleOnUI} from 'react-native-worklets'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type Client, type UriString} from '@atproto/lex'
|
||||
import {type AtUriString, AtUri} from '@atproto/syntax'
|
||||
import {type RichText} from '@bsky.app/sdk/richtext'
|
||||
import {plural} from '@lingui/core/macro'
|
||||
@@ -52,6 +52,7 @@ import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueries, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {matchXrpcError} from '#/lib/xrpc-error'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import * as apilib from '#/lib/api/index'
|
||||
import {EmbeddingDisabledError} from '#/lib/api/resolve'
|
||||
@@ -766,7 +767,9 @@ export const ComposePost = ({
|
||||
|
||||
const getDraftSaveError = useCallback(
|
||||
(e: unknown): string => {
|
||||
if (e instanceof app.bsky.draft.createDraft.DraftLimitReachedError) {
|
||||
if (
|
||||
matchXrpcError(e, app.bsky.draft.createDraft) === 'DraftLimitReached'
|
||||
) {
|
||||
return l`You've reached the maximum number of drafts`
|
||||
}
|
||||
return l`Failed to save draft`
|
||||
@@ -1676,7 +1679,7 @@ let ComposerPost = memo(function ComposerPost({
|
||||
|
||||
const onNewLink = useCallback(
|
||||
(uri: string) => {
|
||||
dispatchPost({type: 'embed_add_uri', uri})
|
||||
dispatchPost({type: 'embed_add_uri', uri: uri as UriString})
|
||||
},
|
||||
[dispatchPost],
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {type UriString} from '@atproto/lex'
|
||||
import {useMemo} from 'react'
|
||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
|
||||
@@ -32,9 +33,9 @@ export const ExternalEmbedGif = ({
|
||||
() =>
|
||||
data && {
|
||||
title: data.title ?? data.uri,
|
||||
uri: data.uri,
|
||||
uri: data.uri as UriString,
|
||||
description: data.description ?? '',
|
||||
thumb: data.thumb?.source.path,
|
||||
thumb: data.thumb?.source.path as UriString | undefined,
|
||||
},
|
||||
[data],
|
||||
)
|
||||
@@ -80,7 +81,7 @@ export const ExternalEmbedLink = ({
|
||||
hasQuote,
|
||||
onRemove,
|
||||
}: {
|
||||
uri: string
|
||||
uri: UriString
|
||||
hasQuote: boolean
|
||||
onRemove: () => void
|
||||
}) => {
|
||||
@@ -100,7 +101,8 @@ export const ExternalEmbedLink = ({
|
||||
description:
|
||||
data.view?.external?.description || data.description,
|
||||
// prefer opengraph data to atproto record-derived image
|
||||
thumb: data.thumb?.source.path || data.view?.external?.thumb,
|
||||
thumb: (data.thumb?.source.path ||
|
||||
data.view?.external?.thumb) as UriString | undefined,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@@ -111,7 +113,7 @@ export const ExternalEmbedLink = ({
|
||||
title: data.title || uri,
|
||||
uri,
|
||||
description: data.description,
|
||||
thumb: data.thumb?.source.path,
|
||||
thumb: data.thumb?.source.path as UriString | undefined,
|
||||
}}
|
||||
hideAlt
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Type converters for Draft API - convert between ComposerState and server Draft types.
|
||||
*/
|
||||
import {type UriString} from '@atproto/lex'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
|
||||
@@ -275,7 +276,7 @@ function serializeGif(gifMedia: {
|
||||
|
||||
return {
|
||||
$type: 'app.bsky.draft.defs#draftEmbedExternal',
|
||||
uri: url.toString(),
|
||||
uri: url.toString() as UriString,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,7 +608,7 @@ export async function draftToComposerPosts(
|
||||
const record = post.embedRecords[0]
|
||||
const urip = new AtUri(record.record.uri)
|
||||
const url = `https://bsky.app/profile/${urip.host}/post/${urip.rkey}`
|
||||
embed.quote = {type: 'link', uri: url}
|
||||
embed.quote = {type: 'link', uri: url as UriString}
|
||||
}
|
||||
|
||||
// Restore link embed (only if not a GIF)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {type UriString} from '@atproto/lex'
|
||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||
import {type AtUriString, toDatetimeString} from '@atproto/syntax'
|
||||
import {RichText} from '@bsky.app/sdk/richtext'
|
||||
@@ -55,7 +56,12 @@ type GifMedia = {
|
||||
|
||||
type Link = {
|
||||
type: 'link'
|
||||
uri: string
|
||||
/*
|
||||
* A URL the user typed or pasted, validated by the link resolver rather than
|
||||
* at construction, so it carries lex's `uri` brand for the record and view
|
||||
* slots it flows into.
|
||||
*/
|
||||
uri: UriString
|
||||
}
|
||||
|
||||
// This structure doesn't exactly correspond to the data model.
|
||||
@@ -90,7 +96,7 @@ export type PostAction =
|
||||
}
|
||||
| {type: 'embed_remove_video'}
|
||||
| {type: 'embed_update_video'; videoAction: VideoAction}
|
||||
| {type: 'embed_add_uri'; uri: string}
|
||||
| {type: 'embed_add_uri'; uri: UriString}
|
||||
| {type: 'embed_remove_quote'}
|
||||
| {type: 'embed_remove_link'}
|
||||
| {type: 'embed_add_gif'; gif: Gif}
|
||||
@@ -645,7 +651,7 @@ export function createComposerState({
|
||||
if (path) {
|
||||
quote = {
|
||||
type: 'link',
|
||||
uri: toBskyAppUrl(path),
|
||||
uri: toBskyAppUrl(path) as UriString,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,7 +705,7 @@ export function createComposerState({
|
||||
if (suggestedExtUri) {
|
||||
link = {
|
||||
type: 'link',
|
||||
uri: suggestedExtUri,
|
||||
uri: suggestedExtUri as UriString,
|
||||
}
|
||||
}
|
||||
const suggestedPostUri = suggestLinkCardUri(
|
||||
@@ -716,7 +722,7 @@ export function createComposerState({
|
||||
if (!quote) {
|
||||
quote = {
|
||||
type: 'link',
|
||||
uri: suggestedPostUri,
|
||||
uri: suggestedPostUri as UriString,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function FeedPage({
|
||||
const isVideoFeed = useMemo(() => {
|
||||
const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri)
|
||||
const feedIsVideoMode =
|
||||
feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo
|
||||
feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo.value
|
||||
const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode
|
||||
return IS_NATIVE && _isVideoFeed
|
||||
}, [feedInfo])
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import {TID} from '@atproto/common-web'
|
||||
import {type DidString, AtUri} from '@atproto/syntax'
|
||||
import {
|
||||
moderateProfile,
|
||||
type ModerationDecision,
|
||||
type ModerationOpts,
|
||||
} from '@bsky.app/sdk/moderation'
|
||||
@@ -21,7 +22,6 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {forceLTR} from '#/lib/strings/bidi'
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import {useCallback, useState} from 'react'
|
||||
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
|
||||
import {moderatePost, type ModerationDecision} from '@bsky.app/sdk/moderation'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {app} from '#/lexicons'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
|
||||
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
|
||||
import {moderatePost, type ModerationDecision} from '@bsky.app/sdk/moderation'
|
||||
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
@@ -8,10 +8,8 @@ import {AtUri} from '@atproto/syntax'
|
||||
import {app} from '#/lexicons'
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {moderatePost} from '#/lib/moderation/subjects'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -64,7 +62,7 @@ export function Post({
|
||||
record
|
||||
? new RichTextAPI({
|
||||
text: record.text,
|
||||
facets: asSdkFacets(record.facets),
|
||||
facets: record.facets,
|
||||
})
|
||||
: undefined,
|
||||
[record],
|
||||
|
||||
@@ -6,6 +6,7 @@ import {Trans} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {getErrorName, getErrorStatus} from '#/lib/xrpc-error'
|
||||
import {app} from '#/lexicons'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
@@ -238,15 +239,21 @@ function detectKnownError(
|
||||
if (!error) {
|
||||
return undefined
|
||||
}
|
||||
/*
|
||||
* Both names are declared by `app.bsky.feed.getAuthorFeed` AND
|
||||
* `.getActorLikes`, and this helper takes an error from an arbitrary feed
|
||||
* descriptor, so the source method is ambiguous - hence the untyped
|
||||
* `getErrorName` check rather than `matchXrpcError`.
|
||||
*/
|
||||
if (
|
||||
error instanceof app.bsky.feed.getAuthorFeed.BlockedActorError ||
|
||||
error instanceof app.bsky.feed.getAuthorFeed.BlockedByActorError
|
||||
getErrorName(error) === 'BlockedActor' ||
|
||||
getErrorName(error) === 'BlockedByActor'
|
||||
) {
|
||||
return KnownError.Block
|
||||
}
|
||||
|
||||
// check status codes
|
||||
if (error?.status === 429) {
|
||||
if (getErrorStatus(error) === 429) {
|
||||
return KnownError.FeedTooManyRequests
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {countLines} from '#/lib/strings/helpers'
|
||||
import {asSdkFacets} from '#/lib/strings/rich-text-helpers'
|
||||
import {
|
||||
POST_TOMBSTONE,
|
||||
type Shadow,
|
||||
@@ -100,7 +99,7 @@ export function PostFeedItem({
|
||||
() =>
|
||||
new RichTextAPI({
|
||||
text: record.text,
|
||||
facets: asSdkFacets(record.facets),
|
||||
facets: record.facets,
|
||||
}),
|
||||
[record],
|
||||
)
|
||||
@@ -253,8 +252,13 @@ let FeedItemInner = ({
|
||||
feedSourceInfo,
|
||||
post: {
|
||||
post,
|
||||
/*
|
||||
* `isType` requires a present `$type` at runtime but narrows to the
|
||||
* schema's input type, whose `$type` is optional, so the `$Typed` arm
|
||||
* of `FeedViewPost['reason']` needs the assertion back.
|
||||
*/
|
||||
reason: bsky.isType(app.bsky.feed.defs.reasonRepost, reason)
|
||||
? reason
|
||||
? (reason as app.bsky.feed.defs.FeedViewPost['reason'])
|
||||
: undefined,
|
||||
feedContext,
|
||||
reqId,
|
||||
|
||||
@@ -2,7 +2,7 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
import {ScrollForwarderView} from 'react-native-scroll-forwarder'
|
||||
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
|
||||
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -14,7 +14,6 @@ import {app} from '#/lexicons'
|
||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||
import {moderateProfile} from '#/lib/moderation/subjects'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
|
||||
Reference in New Issue
Block a user