`UsePreferencesQueryResponse` now derives from the sdk's `BskyPreferences`
rather than the legacy one, which removes the cast the previous slice put on the
assembled response - the query already returns the sdk action's result, so the
two now type structurally.
`labeler.ts`'s three service reads move to the appview client and return
`app.bsky.labeler.defs` views, because `interpretLabelValueDefinitions` takes
the lexicon-typed view. `moderation-opts.tsx` reads `Client.appLabelers`
instead of `AtpAgent.appLabelers`: the lex static is already branded, so the
fallback labeler list satisfies `ModerationPrefsLabeler` without a cast.
`MutedWords` follows the prefs types: `expiresAt` is a `DatetimeString` now,
built with `toDatetimeString` rather than `Date.toISOString`, and
`sanitizeMutedWordValue` comes from `@bsky.app/sdk/utils`.
Point the moderation primitives - the label definitions, the cause/UI/decision
types and the label-info hooks - at `@bsky.app/sdk/moderation` instead of
`@atproto/api`.
The two implementations are mutually unassignable: the sdk's subject types are
the generated `#/lexicons` views, so their `did`/`uri`/`cid` fields are
branded (`DidString`, `AtUriString`) where the `@atproto/api` views type the
same fields as plain `string`. That rules out a partial swap - a decision made
by one implementation cannot be read by the other - so the whole moderation
layer moves together.
`lib/moderation/subjects.ts` is the transitional seam for the brand mismatch.
Every `moderate*` entry point is re-exported through a wrapper that widens its
subject parameter to accept a view from either world, because many read paths
still emit `@atproto/api` views while the moderation runtime only ever reads
`.did`, `.labels` and `.viewer` - none of which the brand affects. It is
deleted in the `@atproto/api` removal pass.
getOtherRequiredData's getPreferences read moves onto the sdk action, which
also unblocks fetchActorDeclarationRecord - its only caller - so the record
read moves to the pds client too.
The prefetch and refetch entry points now take the transport they need
rather than an agent: the appview client for getState and device signals,
the account client for preferences and the declaration record. The session
bundle still carries only an agent, so the three session call sites derive
both clients from it; the full bundle rework is a later slice. The standalone
config read stays on its own unauthenticated agent.
The preferences hooks, nuxs, labeler subscriptions, list mute/block, post
interaction settings, interests and live event preferences move onto
@bsky.app/sdk actions. Preference reads and writes route through the pds
client because they live on the account's actor store, not behind the
appview proxy; the list mute writes stay on the appview client, which is
where that graph endpoint lives.
BskyAgent.getPreferences configured the agent's labelers as a side effect,
so a labeler subscription took effect on the next appview read. The sdk
action does not, so the query now calls configureLabelers explicitly.
Exported hook signatures, query keys and the usePreferencesQuery response
shape are unchanged: the sdk's BskyPreferences is field-for-field identical
to the legacy one apart from branded strings, so the response is cast at
that single seam and the muted-word and nux mutation inputs stay
legacy-typed with a cast into the action.
The post-signup and onboarding writes (setPersonalDetails, upsertProfile,
overwriteSavedFeeds, setInterestsPref) move onto sdk actions over the pds
client, and the starter-pack and contact-import reads move to the lex
clients. Every upsertProfile call now writes a lex blob directly, so the
toLegacyBlobRef bridge has no remaining callers and is deleted.
like/repost/follow/delete and upsertProfile move off the bridge agent's
sugar methods onto @bsky.app/sdk actions over the pds client. The
mute/unmute and thread-mute writes route through the appview client, and
the profile reads move to the appview client behind their existing
legacy-typed hook signatures.
The two RichText classes are not mutually assignable - `UnicodeString` has a
private field and the SDK brands `did`/`uri` as template literal types - so
every producer and consumer of a RichText instance has to move in one step.
`detectFacets` now takes a lex client instead of the legacy agent, which is
what removes the last hard agent dependency from these files. Handle
resolution is an appview job, so the appview client is threaded in: through
`useAppviewClient` in the hooks and dialogs, and through a new
`appviewClient` option on `apilib.post` (Composer already had the client to
hand). The rest of the post pipeline still writes through the agent.
Facet feature checks move from the `AppBskyRichtextFacet` validators to the
generated `#/lexicons` schemas, matching how the rest of the app narrows
lexicon types.
Display sinks still read facets off `@atproto/api` view types, which are the
same lexicon but typed with plain strings. `asSdkFacets` widens them at those
call sites and goes away once the view types come from the SDK too.
aliased npm install of bsky-sdk-preview - it is where RichText and the
moderation helpers now live. its lex-* deps resolve to the copies the app
already uses, so there is a single @atproto/lex in the tree.
App preferences live on the appview and chat preferences on the chat
service, so the combined update mutation holds both clients.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lex client throws XrpcResponseError, which is not an XRPCError
subclass, so the per-method error classes generated by @atproto/api no
longer match. Those instanceof checks compile fine but silently stop
firing, degrading every typed group failure to the generic fallback
message. Narrow against the method schema instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The logged-out join link preview path built an ad-hoc AtpAgent pointed at
CHAT_SERVICE. It is now a module-level unauthenticated lex client against
the same service, so the client is built once rather than per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>