Commit Graph

1511 Commits

Author SHA1 Message Date
Samuel Newman 8da13e7511 drop the casts the dual-world layer needed
With every producer on the generated lexicons, the branded-string assertions at
these 30 sites are no-ops, and oxlint flags them. Also sorts the imports the
type flip disturbed and prunes the suppressions that no longer occur.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:50 +03:00
Samuel Newman 02173556ab drop @atproto/api from the dependency tree
The last references were stale comments; the package itself leaves
package.json, webpack's transpile list, and 102 packages from the lockfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:50 +03:00
Samuel Newman ae0750099f 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>
2026-08-13 22:13:50 +03:00
Samuel Newman dd957dd123 flip the state layer type imports to the generated lexicons
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:49 +03:00
Samuel Newman 541a82c2dd move the bluesky labeler did to the sdk api constant
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:49 +03:00
Samuel Newman 2f482f2c0b repoint the last AtpAgent statics and drop the dead conversions
Five sites read `AtpAgent.appLabelers`, which the previous commit stopped
populating - a silent regression, since an empty static reads as "no
authorities" rather than failing. All now read `Client.appLabelers`, the static
that is actually configured:

- `lib/moderation.ts`'s `isAppLabeler`, which decides whether a labeler is
  non-removable in the UI;
- `preferences/moderation.ts`'s `useMyLabelersQuery`, which seeds the labeler
  list with the app authorities;
- `feed/custom.ts`'s hand-rolled logged-out `getFeed`, which reproduces the
  header lex would have emitted because the request bypasses the client.

`sessionAccountToSession` (the `AtpSessionData` conversion) had no callers left
once the bridge agent went, and the age assurance config read - the one
standalone `new AtpAgent` the age assurance slice deliberately left behind -
moves to the public appview client, which is what it was approximating.

That is the last `AtpAgent` in src. What remains of `@atproto/api` in the
session layer is types and `BSKY_LABELER_DID`, which S4d owns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:48 +03:00
Samuel Newman 64b0039c67 hold the three clients in the bundle and delete the bridge agent
The bundle becomes `{session, appviewClient, pdsClient, chatClient, service}`
and the hooks read those fields directly, so `useAgent` and the three
`agentTo*Client` memo maps go with the agent. The WeakMaps existed only because
clients were derived from a long-lived agent; a bundle that HOLDS its clients
gets identity stability for free.

The clients are now the sole producers of their own headers, which is what lets
`bridge-agent.ts` (442 lines) and `agent.ts` go:

- the appview client passes BLUESKY_PROXY_HEADER as its `service`, so it emits
  atproto-proxy itself rather than inheriting it from a configureProxy call
  sequenced after the PDS-targeting setup;
- labeler subscriptions go on the appview instance via `setLabelers`, and the
  global `;redact` authorities come from the `Client` static alone. Both halves
  of the double-emit hazard the previous slice documented are closed by there
  being one producer instead of two, so the appview client no longer suppresses
  `appLabelers` - only the PDS and chat clients do, because those services take
  no moderation authorities at all;
- `configureGlobalAppLabelers` drops its `AtpAgent.configure` half.

PDS routing for the pre-didDoc window is `routeSessionToPds`, a 10-line shim
replacing the manager's dispatchUrl/extractPdsUrl/identity-cache apparatus.
`PasswordSession` resolves each request against `extractPdsUrl(didDoc) ??
service`, so an entryway account with no didDoc yet - the synchronous resume
fast path, i.e. the common cold start - would send every request to the
entryway. The shim absolutizes against the stored url first, which survives
`new URL(path, base)` untouched. It pins that url for the bundle's lifetime
where the manager would have preferred a later didDoc endpoint; that only
differs if the account's PDS moved, and the next cold start pins the new one.
Four cases in clients-test cover this, including the two the old suite could
not express (pinned-vs-didDoc precedence, and didDoc routing with nothing
pinned).

`createPublicSessionBundle` now runs `configureModerationForGuest` itself. That
is a behavior fix, not a refactor: it populates the `Client.appLabelers` static
the public appview client reads, and with no agent left to stamp the header a
logged-out read would otherwise carry no moderation authorities.

finishPreparation, the kill-switch disposal, the redacted logging, refreshSession
and the expiry-rescue path are unchanged - only how the bundle is constructed
moved. Disposal is now just the hook kill: the clients hold no state, and every
request they make goes through the session's injected fetch.

bridge-agent-test.ts is deleted with its subject. clients-test.ts is rewritten
against session-built clients, keeping every header canary (exactly-once on the
appview, none on pds/chat, exact proxy values, the throwing client's cause
chain). The three provider suites and session-core-test are adapted in place -
their constructions change, their assertions do not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:48 +03:00
Samuel Newman 3f7b12b2fe migrate the remaining agent reads outside the session layer
The last non-session `useAgent` consumers, all thin aliases over calls the
clients already make:

- `handle.ts`: `getProfile` / `resolveHandle` to the appview client.
- `post.ts`: the three post readers share one `fetchPost` helper on the appview
  client. Their consumers still want `@atproto/api` views, so the generated view
  is asserted across at that single boundary rather than at each call site.
- `post-feed.ts`: `agent.session` gated the logged-out "did any post survive
  moderation" assertion, which is a question about the session, not the
  transport - it reads `hasSession` from the session context now.
- `ChangeHandleDialog`: `agent.serviceUrl` becomes `currentAccount.service`.
- `TestCtrls.e2e`: drops the `configureProxy` call. The appview client reads
  `BLUESKY_PROXY_HEADER` when the bundle builds it, and the gate around this
  input means no bundle exists yet, so setting the constant is sufficient.

`preferences/index.ts` still writes labeler subscriptions to the agent; that
one moves with the bundle rework, which is what gives it a client to write to.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:48 +03:00
Samuel Newman 92408c62a6 migrate link resolution to the appview and chat clients
`resolveLink` took an agent and used it for four appview reads plus the chat
invite preview, so it now takes both clients as a `LinkResolvers` pair - the
caller cannot know which branch a URL will take until it is parsed. That was
the last DM_SERVICE_HEADERS site, so the constant is deleted.

`resolveGif` never touched the agent at all (it is pure URL metadata work on
what the picker already returned), so its parameter is dropped rather than
replaced, along with the one on `fetchResolveGifQuery`.

With the resolvers on clients, `apilib.post` loses the agent parameter the
previous slice kept solely for them, and `composerStateToDraft` takes the
resolver pair instead of minting a throwaway public agent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:48 +03:00
Samuel Newman 9bbc3befdd migrate the stateful chat agents to the chat client
Convo and MessagesEventBus were the last two useAgent consumers holding a
long-lived agent rather than reading one per render. Both now take a chat
client, which carries the atproto-proxy header itself, so the per-call
DM_SERVICE_HEADERS drop out under the double-set rule.

Both providers gained an effect that pushes the current client into the
instance. The instances are created once with useState and outlive the client
they were built with: an account switch, a cross-tab token sync or an expiry
rescue replaces the bundle and disposes the previous clients, so without the
sync a convo would keep sending through a dead session.

`handleSendMessageFailure` narrows on XrpcResponseError rather than XRPCError.
The lex client throws the former, so the status branch (and the recoverable /
unrecoverable split that drives the retry banner) had silently stopped firing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:48 +03:00
Samuel Newman 5f539827da set the global app labelers on the lex Client static
`configureGlobalAppLabelers` now writes both `AtpAgent.configure` and
`Client.configure`, so a client built without a wrapped agent carries the
global `;redact` moderation authorities. That is what the logged-out appview
client needs: it has no agent underneath to stamp the header, and after the
bundle rework no client will.

Flipping the static alone double-emits, which two probe runs confirmed. Neither
producer dedupes against the other - the agent joins its list with whatever is
already on the request while lex collects into a `Set` keyed on the
`;redact`-suffixed string - so a DID in both statics produced
`did:plc:x;redact, did:plc:x;redact`. Separately, the PDS and chat clients wrap
the session manager *below* the agent's header layer and so emitted no labelers
at all; the static would have started adding them to non-appview requests.

Both are closed by building the three agent-wrapping clients with
`appLabelers: null`, which suppresses the class-wide static per instance. That
leaves exactly one producer on an appview request and none on a PDS or chat one,
while the static stays populated for the clients that read only it. The
labeler-header tests cover all three surfaces and fail without the suppression.

The per-account subscriptions stay on the agent for now, behind
`applyLabelersToClient`, because the agent is still what stamps the header on
the wrapping client's requests. The bundle-rework slice repoints that one
function body at `appviewClient.setLabelers` and drops the agent statics; the
`Client.configure` half of `configureGlobalAppLabelers` and the
`appLabelers: null` on the appview client both stay as they are.

`configureModerationForAccount` takes the bundle rather than a bare agent,
matching the signature the bundle rework needs.
2026-08-13 22:13:47 +03:00
Samuel Newman de09c83f99 point the state and feature moderation call sites at the sdk
Query, notification and feature-level moderation reads switch to the sdk types
and take their `moderate*`/`hasMutedWord` entry points from the
`lib/moderation/subjects` seam, since these paths still carry
`@atproto/api`-typed views.

`shouldFilterNotif` is the one behavioral seam worth noting: its notification
and facet values come from an unmigrated read, so the mute-word check goes
through the widening wrapper rather than the sdk export directly.
2026-08-13 22:13:47 +03:00
Samuel Newman f1411df0a4 flip the preferences and labeler reads to sdk types
`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`.
2026-08-13 22:13:47 +03:00
Samuel Newman 5e9a437f53 fix mute-reposts hook 2026-08-13 22:13:47 +03:00
Samuel Newman 755ce365f6 migrate the age assurance data reads to the session clients
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.
2026-08-13 22:13:47 +03:00
Samuel Newman 17c17b4543 migrate the preference read-modify-writes to sdk actions
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.
2026-08-13 22:13:46 +03:00
Samuel Newman a40c199523 migrate the signup and onboarding profile writes to sdk actions
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.
2026-08-13 22:13:46 +03:00
Samuel Newman 926abd0566 migrate the record-mint mutations to sdk actions
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.
2026-08-13 22:13:46 +03:00
Samuel Newman 88bc368991 drop the agent url reads in link meta and push registration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:46 +03:00
Samuel Newman 483e79497b add refreshSession to the session api and migrate its callers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 83fdc95237 migrate the blob upload helper to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 89db69ce55 inline the did reads, drop the getDidFromClient util
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 4c5294a1a2 migrate the status, germ declaration and block records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 3a970a7104 migrate the starter pack writes to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman a86590ad4b migrate the list and listitem records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 780fccdbbe migrate the chat and notification declaration records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman ed652ad484 migrate the verification and chat declaration records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:45 +03:00
Samuel Newman 0c8cdba703 migrate the threadgate and postgate records to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:44 +03:00
Samuel Newman 74b0001335 migrate getAllListMembers to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:13:44 +03:00
Samuel Newman 2beb63fb2d migrate RichText to the SDK and resolve facets via the appview
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.
2026-08-13 21:50:54 +03:00
Samuel Newman 09ca1307bc migrate the app password and update handle mutations to the pds client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:54 +03:00
Samuel Newman a92f28461b migrate the pre-auth service calls to the service client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman 88477d0767 migrate the thread mute backfill to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman 0bf2ada0c6 migrate the activity subscription queries to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman f2a1a6ceb9 migrate the notification settings queries to the appview and chat clients
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>
2026-08-13 21:50:53 +03:00
Samuel Newman c1a318905b migrate the notification feed and unread checker to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman c857a534ec migrate the post social queries and feed api construction sites
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman 5e88752fc0 migrate pds detection handle resolution to the public appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:53 +03:00
Samuel Newman 6e59eeb9a6 migrate the feed source and feed feedback calls to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 1a6bfd8a6a migrate the contact queries and phone verification to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 8a87a5fdea migrate the bookmark and draft queries to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman c0aafe4ffd migrate the actor search and autocomplete queries to the appview client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 38042dcc02 note why the public chat client skips the network-aware fetch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 74f4532922 migrate the join link and join request queries to the chat client
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>
2026-08-13 21:50:52 +03:00
Samuel Newman 304726b83d migrate the group membership queries to the chat client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 9553dd374b Update get-convo-availability.ts
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 2c779615df guard the unauthed chat status queries
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman aad0846ec0 cast branded chat convo params with the syntax types
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 195c7a8d4a migrate the chat convo lifecycle mutations to the chat client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:52 +03:00
Samuel Newman 6b9b18cba4 migrate the chat convo read queries to the chat client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 21:50:51 +03:00