diff --git a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx index 722c0594e5..10426d0ca6 100644 --- a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx @@ -61,7 +61,6 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', - // the persisted account did is already resolved did: currentAccount.did, }, reason: `AGE_ASSURANCE_INQUIRY: ` + details, diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index c22a93fce9..86a0c8cca6 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -67,7 +67,7 @@ export function useSubmitReportMutation() { reason: state.details, subject: { $type: 'com.atproto.admin.defs#repoRef', - // the parsed subject carries an already-resolved did + // the parsed subject holds the did as a plain string did: subject.did as DidString, }, } diff --git a/src/features/liveNow/index.tsx b/src/features/liveNow/index.tsx index de786ba4eb..cc9dd21fbb 100644 --- a/src/features/liveNow/index.tsx +++ b/src/features/liveNow/index.tsx @@ -276,7 +276,6 @@ export function useUpsertLiveStatusMutation( } satisfies app.bsky.actor.status.Main const upsert = async () => { - // the session account is still legacy-typed, so its did is unbranded const repo = currentAccount.did const collection = 'app.bsky.actor.status' diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index 6185f788df..f34d1d5601 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -136,7 +136,7 @@ async function loggedOutFetch({ ) /* * The response is hand-decoded rather than validated, so the lex output shape - * is asserted here just as the old-world one was. + * is asserted here. */ let data = res.ok ? (lexParse(await res.text()) as app.bsky.feed.getFeed.$OutputBody) diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index af2d54e65a..990d41c8d1 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -307,11 +307,6 @@ async function resolveEmbed( if (resolvedLink.type === 'record') { return { $type: 'app.bsky.embed.record', - /* - * `resolve.ts` is still legacy-typed - its strong refs and URIs carry - * unbranded strings. Assert at the boundary until it moves to the - * clients. - */ record: resolvedLink.record, } } @@ -495,11 +490,6 @@ async function resolveMedia( return undefined } -/* - * `resolve.ts` still returns legacy-typed views, so its strong refs carry plain - * strings where the record write wants the branded syntax types; assert at the - * boundary until those views come from the generated lexicons. - */ async function resolveRecord( clients: LinkResolvers, queryClient: QueryClient, diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index c90f1a6fb9..08ecf4b566 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -1,4 +1,4 @@ -import {AtUri} from '@atproto/syntax' +import {type AtIdentifierString, AtUri} from '@atproto/syntax' import {parse} from 'psl' import TLDs from 'tlds' @@ -43,8 +43,8 @@ export function makeRecordUri( rkey: string, ) { const urip = new AtUri('at://placeholder.placeholder/') - // @ts-expect-error TODO new-sdk-migration - urip.host = didOrName + // the helper takes the did or handle as a plain string + urip.host = didOrName as AtIdentifierString urip.collection = collection urip.rkey = rkey return urip.toString() diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx index 85e89d9315..b330113d19 100644 --- a/src/screens/Messages/components/ChatDisabled.tsx +++ b/src/screens/Messages/components/ChatDisabled.tsx @@ -105,7 +105,6 @@ function DialogInner() { reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', - // the persisted account did is already resolved did: currentAccount.did, }, reason: details, diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 1ef81f006d..586809c7a2 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -567,10 +567,6 @@ export function MessagesList({ if (post) { embed = { $type: 'app.bsky.embed.record', - /* - * `getPost` hands back `uri` and `cid` as plain strings rather - * than the branded syntax types the lexicon input declares. - */ record: { uri: post.uri, cid: post.cid, diff --git a/src/screens/ModerationInteractionSettings/index.tsx b/src/screens/ModerationInteractionSettings/index.tsx index 9ce833a8e5..c6ea9db622 100644 --- a/src/screens/ModerationInteractionSettings/index.tsx +++ b/src/screens/ModerationInteractionSettings/index.tsx @@ -71,10 +71,6 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) { $type: 'app.bsky.feed.threadgate', post: '' as AtUriString, createdAt: toDatetimeString(new Date()), - /* - * Preferences are still typed against the legacy client, so the stored - * rules arrive unbranded. Wave B migrates `getPreferences`. - */ allow: preferences.postInteractionSettings.threadgateAllowRules, }) }, [preferences.postInteractionSettings.threadgateAllowRules]) diff --git a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx index 450bf6da15..ecbc7a9c4d 100644 --- a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx +++ b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx @@ -75,7 +75,6 @@ export function StarterPackCard({ let followUris: Map try { followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { - // the starter pack view is still legacy-typed uri: view.uri, cid: view.cid, }) diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index af8a40f092..07ccc2f17a 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -21,7 +21,7 @@ export async function bulkWriteFollows( const followRecords: $Typed[] = dids.map(did => { return { $type: 'app.bsky.graph.follow', - // callers hold plain dids read off legacy-typed views + // the helper takes the dids as plain strings subject: did as DidString, createdAt: toDatetimeString(new Date()), via, diff --git a/src/screens/Profile/components/GermButton.tsx b/src/screens/Profile/components/GermButton.tsx index f79c503251..59d3d5d6bb 100644 --- a/src/screens/Profile/components/GermButton.tsx +++ b/src/screens/Profile/components/GermButton.tsx @@ -124,7 +124,7 @@ function GermSelfButton({did}: {did: string}) { const {mutate: deleteDeclaration, isPending} = useMutation({ mutationFn: async () => { const previousRecord = await pdsClient - // the profile view is still legacy-typed, so its did is unbranded + // the component takes the did as a plain string prop .get(com.germnetwork.declaration, { repo: did as DidString, rkey: 'self', diff --git a/src/screens/Settings/components/DeleteAccountDialog.tsx b/src/screens/Settings/components/DeleteAccountDialog.tsx index 892b4ce6b5..d2d687bc79 100644 --- a/src/screens/Settings/components/DeleteAccountDialog.tsx +++ b/src/screens/Settings/components/DeleteAccountDialog.tsx @@ -125,7 +125,6 @@ function DeleteAccountDialogInner({ */ await chatClient.call(chat.bsky.actor.deleteAccount) await client.call(com.atproto.server.deleteAccount, { - // the persisted account did is already resolved did: currentAccount.did, password, token, diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 658241c660..b023889300 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -376,7 +376,6 @@ function Header({ let followUris: Map try { followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { - // the starter pack view is still legacy-typed uri: starterPack.uri, cid: starterPack.cid, }) diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx index a48f264545..60517295f5 100644 --- a/src/screens/Takendown.tsx +++ b/src/screens/Takendown.tsx @@ -56,7 +56,6 @@ export function Takendown() { reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', - // the persisted account did is already resolved did: currentAccount.did, }, reason: appealText, diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index e8310a113b..171a2240ad 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -8,18 +8,22 @@ import {PlatformInfo} from '../../../modules/expo-bluesky-swiss-army' const externalEmbedOptions = ['show', 'hide'] as const +/** + * Types a persisted string field with a branded type WITHOUT validating the + * brand at runtime. Persisted values predate the brands and must never fail + * schema validation over one (that would drop the account on upgrade). + */ +function unvalidatedBranded(): z.ZodType { + return z.string() as unknown as z.ZodType +} + /** * A account persisted to storage. Stored in the `accounts[]` array. Contains * base account info and access tokens. */ const accountSchema = z.object({ service: z.string(), - /* - * Branded at the type level only. The runtime check stays a bare `z.string()` - * so already-persisted accounts can never fail validation over the brand, - * which would log the user out on upgrade. - */ - did: z.string() as unknown as z.ZodType, + did: unvalidatedBranded(), handle: z.string(), email: z.string().optional(), emailConfirmed: z.boolean().optional(), diff --git a/src/state/queries/activity-subscriptions.ts b/src/state/queries/activity-subscriptions.ts index e2753a43c1..5b52fc7e16 100644 --- a/src/state/queries/activity-subscriptions.ts +++ b/src/state/queries/activity-subscriptions.ts @@ -40,7 +40,6 @@ export function useNotificationDeclarationQuery() { queryFn: async () => { try { const response = await client.get(app.bsky.notification.declaration, { - // the session account is still legacy-typed, so its did is unbranded repo: currentAccount!.did, rkey: 'self', }) diff --git a/src/state/queries/bookmarks/useBookmarksQuery.ts b/src/state/queries/bookmarks/useBookmarksQuery.ts index adb12a47ce..2eeab4c213 100644 --- a/src/state/queries/bookmarks/useBookmarksQuery.ts +++ b/src/state/queries/bookmarks/useBookmarksQuery.ts @@ -1,5 +1,5 @@ import {type $Typed} from '@atproto/lex' -import {AtUri} from '@atproto/syntax' +import {AtUri, toDatetimeString} from '@atproto/syntax' import { type InfiniteData, type QueryClient, @@ -71,19 +71,14 @@ export async function optimisticallySaveBookmark( pages: data.pages.map((page, index) => { if (index === 0) { post.$type = 'app.bsky.feed.defs#postView' - /* - * The optimistic entry is synthesized with unbranded string - * fields, so it is asserted to the generated view type the query - * data is keyed on. - */ - const bookmark = { - createdAt: new Date().toISOString(), + const bookmark: app.bsky.bookmark.defs.BookmarkView = { + createdAt: toDatetimeString(new Date()), subject: { uri: post.uri, cid: post.cid, }, item: post as $Typed, - } as unknown as app.bsky.bookmark.defs.BookmarkView + } return { ...page, bookmarks: [bookmark, ...page.bookmarks], diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts index 4d4c35e56c..01bc795a31 100644 --- a/src/state/queries/list-memberships.ts +++ b/src/state/queries/list-memberships.ts @@ -44,8 +44,8 @@ export function useListMembershipAddMutation({ throw new Error('Not signed in') } /* - * The mutation's inputs are plain strings held by legacy-typed views, so - * they are asserted to their branded forms here. + * The mutation takes the list uri and actor did as plain strings, so they + * are asserted to their branded forms here. */ const res = await pdsClient.create(app.bsky.graph.listitem, { subject: actorDid as DidString, diff --git a/src/state/queries/list.ts b/src/state/queries/list.ts index 959dfbb58c..85efd85b15 100644 --- a/src/state/queries/list.ts +++ b/src/state/queries/list.ts @@ -189,7 +189,6 @@ export function useListDeleteMutation() { let listitemRecordUris: string[] = [] for (let i = 0; i < 100; i++) { const res = await pdsClient.list(app.bsky.graph.listitem, { - // the session account is still legacy-typed, so its did is unbranded repo: currentAccount.did, cursor, limit: 100, diff --git a/src/state/queries/lists-with-membership.ts b/src/state/queries/lists-with-membership.ts index 9aac39197c..1ef035c24a 100644 --- a/src/state/queries/lists-with-membership.ts +++ b/src/state/queries/lists-with-membership.ts @@ -1,4 +1,4 @@ -import {type AtIdentifierString} from '@atproto/syntax' +import {type AtIdentifierString, type AtUriString} from '@atproto/syntax' import { type InfiniteData, type QueryClient, @@ -72,18 +72,13 @@ export function updateListMembershipOptimistically({ ...page, listsWithMembership: page.listsWithMembership.map(lwm => { if (lwm.list.uri === listUri) { - /* - * Callers hand over a plain at-uri and an old-world ProfileView, - * whose `uri`/`did` are unbranded strings. They are runtime - * identical to the generated branded forms, so the synthesised - * listItem is asserted rather than re-validated. - */ return { ...lwm, listItem: { - uri: membershipUri, + // callers hand over the membership uri as a plain string + uri: membershipUri as AtUriString, subject, - } as app.bsky.graph.defs.ListItemView, + }, } } return lwm diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts index edaea20d0e..e581040ce9 100644 --- a/src/state/queries/messages/actor-declaration.ts +++ b/src/state/queries/messages/actor-declaration.ts @@ -40,7 +40,6 @@ export function useUpdateActorDeclaration({ current?.associated?.chat?.allowGroupInvites, }) const result = await pdsClient.call(com.atproto.repo.putRecord, { - // the session account is still legacy-typed, so its did is unbranded repo: currentAccount.did, collection: 'chat.bsky.actor.declaration', rkey: 'self', diff --git a/src/state/queries/nuxs/index.ts b/src/state/queries/nuxs/index.ts index dc69b1fcde..d8c9e71924 100644 --- a/src/state/queries/nuxs/index.ts +++ b/src/state/queries/nuxs/index.ts @@ -103,10 +103,6 @@ export function useSaveNux() { return useMutation({ retry: 3, mutationFn: async (nux: AppNux) => { - /* - * `serializeAppNux` still returns the legacy `Nux`, whose strings are - * unbranded; it is validated against the same schema the action expects. - */ await pdsClient.call(upsertNux, serializeAppNux(nux)) // triggers a refetch await queryClient.invalidateQueries({ diff --git a/src/state/queries/pinned-post.ts b/src/state/queries/pinned-post.ts index 8e36207e40..b1364725ca 100644 --- a/src/state/queries/pinned-post.ts +++ b/src/state/queries/pinned-post.ts @@ -46,7 +46,7 @@ export function usePinnedPostMutation() { profile, updates: existing => { existing.pinnedPost = pinCurrentPost - ? // the caller's uri/cid are unbranded strings + ? // the mutation takes the uri/cid as plain strings ({ uri: postUri, cid: postCid, diff --git a/src/state/queries/postgate/util.ts b/src/state/queries/postgate/util.ts index b9a3218b2c..f57fe0479d 100644 --- a/src/state/queries/postgate/util.ts +++ b/src/state/queries/postgate/util.ts @@ -8,8 +8,8 @@ export const POSTGATE_COLLECTION = 'app.bsky.feed.postgate' /** * Create a new {@link app.bsky.feed.postgate.Main}. URIs are accepted as plain - * strings (callers hold raw AT-URIs, often from legacy-typed views) and - * asserted to the branded `AtUriString` here. + * strings (callers hold raw AT-URIs) and asserted to the branded `AtUriString` + * here. */ export function createPostgateRecord( postgate: Omit< diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index d0a4379aef..190f4a3998 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -643,7 +643,7 @@ function useProfileBlockMutation() { throw new Error('Not signed in') } return await pdsClient.create(app.bsky.graph.block, { - // the profile view is still legacy-typed, so its did is unbranded + // the mutation takes the did as a plain string subject: did as DidString, createdAt: toDatetimeString(new Date()), }) diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts index 3a30b30caa..dd3ff1a9fe 100644 --- a/src/state/queries/starter-packs.ts +++ b/src/state/queries/starter-packs.ts @@ -253,9 +253,9 @@ export function useEditStarterPackMutation({ list: currentStarterPack.list?.uri, /* * Pre-existing quirk preserved verbatim: the edit path writes whole - * `GeneratorView`s where the lexicon declares `feedItem` refs. lex - * types the raw `putRecord` body as a `LexValue`, which the legacy - * view interface does not structurally satisfy, hence the cast. + * `GeneratorView`s where the lexicon declares `feedItem` refs. The + * raw `putRecord` body is typed as a lex `LexValue`, which the view + * structurally satisfies, so this passes through unchanged. */ feeds: feeds, createdAt: currentStarterPack.record.createdAt, diff --git a/src/state/queries/threadgate/util.ts b/src/state/queries/threadgate/util.ts index 4e64835f4d..5a9fa4aea9 100644 --- a/src/state/queries/threadgate/util.ts +++ b/src/state/queries/threadgate/util.ts @@ -5,10 +5,8 @@ import {app} from '#/lexicons' import * as bsky from '#/types/bsky' /* - * Threadgate VIEWS stay on the legacy client types: they are read-only inputs - * from the appview, and branding them here would ripple through every post - * component. Only the threadgate RECORD is migrated, because it is written - * through `com.atproto.repo.putRecord`, whose body is typed as a lex `LexMap`. + * The view's `record` is typed as an opaque lex `LexMap`, so it is validated + * against the threadgate record schema before its `allow` rules are read. */ export function threadgateViewToAllowUISetting( threadgateView: app.bsky.feed.defs.ThreadgateView | undefined, diff --git a/src/state/queries/verification/useVerificationCreateMutation.tsx b/src/state/queries/verification/useVerificationCreateMutation.tsx index cd52a62a1f..50510bac9e 100644 --- a/src/state/queries/verification/useVerificationCreateMutation.tsx +++ b/src/state/queries/verification/useVerificationCreateMutation.tsx @@ -22,7 +22,6 @@ export function useVerificationCreateMutation() { } const {uri} = await pdsClient.create(app.bsky.graph.verification, { - // the profile view is still legacy-typed, so its strings are unbranded subject: profile.did, createdAt: toDatetimeString(new Date()), handle: profile.handle, diff --git a/src/state/queries/verification/useVerificationsRemoveMutation.tsx b/src/state/queries/verification/useVerificationsRemoveMutation.tsx index 0de675f0d2..e0e0fe5e5a 100644 --- a/src/state/queries/verification/useVerificationsRemoveMutation.tsx +++ b/src/state/queries/verification/useVerificationsRemoveMutation.tsx @@ -50,7 +50,6 @@ export function useVerificationsRemoveMutation() { }, () => { return appviewClient.call(app.bsky.actor.getProfile, { - // the profile view is still legacy-typed, so its did is unbranded actor: profile.did ?? '', }) }, diff --git a/src/state/unstable-post-source.tsx b/src/state/unstable-post-source.tsx index e374cbbeae..4386dec5e7 100644 --- a/src/state/unstable-post-source.tsx +++ b/src/state/unstable-post-source.tsx @@ -1,5 +1,5 @@ import {useEffect, useId, useState} from 'react' -import {AtUri} from '@atproto/syntax' +import {type AtIdentifierString, AtUri} from '@atproto/syntax' import {Logger} from '#/logger' import {type FeedSourceInfo} from '#/state/queries/feed' @@ -82,8 +82,8 @@ export function useUnstablePostSource(key: string) { */ export function buildPostSourceKey(key: string, handle: string) { const urip = new AtUri(key) - // @ts-expect-error TODO new-sdk-migration - urip.host = handle + // the handle arrives as a plain string + urip.host = handle as AtIdentifierString return urip.toString() } diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts index 7f4840c7bf..ec786239a4 100644 --- a/src/view/com/composer/drafts/state/queries.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -143,10 +143,6 @@ export function useSaveDraftMutation() { {appviewClient: client, chatClient}, composerState, ) - /* - * `composerStateToDraft` builds the draft with unbranded string fields, - * so it is asserted once here to the generated input type. - */ const draft = apiDraft logger.debug('saving draft', { diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts index 228f9a62ce..ad3e5f4df6 100644 --- a/src/view/com/composer/state/composer.ts +++ b/src/view/com/composer/state/composer.ts @@ -321,10 +321,6 @@ export function composerReducer( posts, postgate: createPostgateRecord({ post: '', - /* - * Draft records are still typed against the legacy client, so the - * stored rules arrive unbranded. Wave B migrates the draft types. - */ embeddingRules: postgateEmbeddingRules, }), threadgate: threadgateRecordToAllowUISetting({ @@ -750,10 +746,6 @@ export function createComposerState({ ], postgate: createPostgateRecord({ post: '', - /* - * Preferences are still typed against the legacy client, so the stored - * rules arrive unbranded. Wave B migrates `getPreferences`. - */ embeddingRules: initInteractionSettings?.postgateEmbeddingRules || [], }), threadgate: threadgateRecordToAllowUISetting({ diff --git a/src/view/com/composer/threadgate/ThreadgateBtn.tsx b/src/view/com/composer/threadgate/ThreadgateBtn.tsx index 9e730b48d8..388c5e2be0 100644 --- a/src/view/com/composer/threadgate/ThreadgateBtn.tsx +++ b/src/view/com/composer/threadgate/ThreadgateBtn.tsx @@ -86,10 +86,6 @@ export function ThreadgateBtn({ $type: 'app.bsky.feed.threadgate', post: '' as AtUriString, createdAt: toDatetimeString(new Date()), - /* - * Preferences are still typed against the legacy client, so the stored - * rules arrive unbranded. Wave B migrates `getPreferences`. - */ allow: preferences?.postInteractionSettings.threadgateAllowRules, }) const prefPostgate = createPostgateRecord({ diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 0087396bb8..d4d4fe52b3 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -913,8 +913,6 @@ function SayHelloBtn({profile}: {profile: app.bsky.actor.defs.ProfileView}) { try { setIsLoading(true) const data = await client.call(chat.bsky.convo.getConvoForMembers, { - // both dids are already resolved - one from the profile view, one from - // the active session members: [profile.did, currentAccount!.did], }) navigation.navigate('MessagesConversation', {