Commit Graph

7902 Commits

Author SHA1 Message Date
Samuel Newman eb620631f7 remove redundant pds routing options 2026-07-31 16:08:11 +03:00
Samuel Newman 37822a523a clean up session clients and lifecycle 2026-07-31 16:07:52 +03:00
Samuel Newman 436369e9d4 rm useless type 2026-07-31 15:01:53 +03:00
Samuel Newman 4720ef3c91 cull comments 2026-07-31 14:58:48 +03:00
Samuel Newman a747a4dbe2 Update pnpm-lock.yaml 2026-07-30 21:54:43 +03:00
Samuel Newman 275ff4f94b reinstall deps 2026-07-30 21:38:54 +03:00
Samuel Newman 0ed770be93 fix rebase conflicts 2026-07-30 21:10:22 +03:00
Samuel Newman c582230659 collapse session bundle to a single bsky client
lex-client 0.3.0 record helpers auto-target the account host per call,
so the separate header-less account client is unnecessary: one client
with the appview proxy service covers queries and writes. Raw
com.atproto server/identity/repo calls that must hit the PDS pass
service: null explicitly. Hook names unchanged; usePdsClient now
marks "must target the account host" call sites and returns a
throwing client when logged out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:51 +03:00
Samuel Newman a44b5458b4 unpin lex-client, drop 0.2.4 export-car workaround
The pnpm override pinned @atproto/lex-client to 0.2.4, predating both
lex-client 0.3.0 and sdk preview.2 - which assumes 0.3.0 semantics
(per-call service: null is a no-op on 0.2.4). 0.3.0 also returns
non-JSON encodings raw, so the ExportCarDialog fetchHandler
workaround for mis-parsed application/jsonl is unnecessary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:51 +03:00
Samuel Newman 546ee843c2 fix logout bug: expiry rescue, resume bail, stale-bundle event drops
Three production logout root causes (Logout issues Linear project):
- a stale tab's ExpiredToken no longer clears newer tokens; the expiry
  path compares against the latest persisted generation and rescues
  from it (loop-guarded) instead of logging every tab out
- follower-tab resumeSession disposes its bundle and bails when a
  logout or account switch raced it; aborted resumes now dispose too
- the reducer drops ALL events from non-current bundles, so a refresh
  landing mid-logout can't resurrect cleared tokens; session-dropped
  toast gated on the bundle still being current

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman 44b0ba060b collapse cross-tab rebuild to a single synchronous tick
With moderation config sync, the bundle swap no longer defers behind
an async labeler read. The race guard from the review stays as a
defensive invariant against reintroducing an await.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman 63dad6320e move labeler cache to mmkv, make moderation config sync
The per-account labeler cache was the only async dependency in
configureModerationForAccount. MMKV-backed sync reads let session
bundle prep apply labelers in the same tick. No AsyncStorage
backfill: the cache is rewritten on every preferences fetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman 4d25c884c7 emit moderation labeler only via global appLabelers
The per-client labelers list also carried api.moderation.did, which
the header builder emits plain alongside the global did;redact entry -
a duplicate that could weaken redaction. Per-client lists now carry
only account subscriptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman ffba34c548 add createLexClient factory with strictResponseProcessing: false
lex-client's strict mode rejects the legacy blob reference format
({cid, mimeType}) still present in older records; the old stack
tolerated it. Route all client construction through the factory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman 5541a48552 bump sdk preview to 0.0.1-preview.2, lex 0.3.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:21:02 +03:00
Samuel Newman 4616e50d4b chat event bus: tolerate mid-flight session bundle swaps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:20:11 +03:00
Samuel Newman 3d7cee234d restore metadata dep in analytics useChild memo
The phase-2 session rework dropped `metadata` from the child logger's
useMemo deps to satisfy exhaustive-deps (which misclassifies the
closure-captured value as outer-scope because createLogger is defined
outside a component - lint-staged's --fix strips the dep on commit).
But AnalyticsContext explicitly re-renders (not remounts) on account
change with a new did, producing a fresh createLogger closure with new
metadata; a mounted consumer's memoized child logger kept the previous
account's metadata, misattributing telemetry across session switches.
Suppress the rule with the why and keep the dep.

Found by a full-diff roast pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:20:11 +03:00
Samuel Newman 1ea30c64f0 fix isXrpcErrorOf mismatch test to actually test a mismatch
The 'different error code' test asserted a MATCHING code returns true
(with an unrelated method as witness), contradicting its own name and
leaving the code-mismatch path untested. Split into two tests: a real
mismatch (approveJoinRequest declares both names, so no as-any cast
needed) asserting false, and the non-XRPC case.

Found by a roast pass over the error-matching changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman 934d2e2170 restore compile-time error matching with isXrpcErrorOf
The migration replaced typed error classes (e instanceof
SomeNsid.SomeError) with getErrorName(e) === 'X' string comparisons,
where a typo'd or undeclared name compiles silently. isXrpcErrorOf
(method, e, name) constrains name to InferMethodError<M> - the errors
tuple the generated lexicon schema declares - restoring the old
compile-time guarantee (verified: a typo'd name fails typecheck).
47 comparison sites across 16 files migrated.

Deliberately left on getErrorName, with comments:
- LoginForm AuthFactorTokenRequired: a LexAuthFactorError from
  PasswordSession.login, not an XrpcError.
- AppealForm AlreadyAppealed: createReport declares NO errors in its
  lexicon; the server sends it anyway (undeclared behavior worth
  raising upstream).
- PostFeedErrorMessage Block detection: generic over feed descriptors,
  source method genuinely ambiguous.
- isErrorMaybeAppPasswordPermissions: generic InvalidToken + message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman f1afc3c47c match app-password errors on the typed lex error, not raw message equality
isErrorMaybeAppPasswordPermissions compared getErrorName against
'TokenInvalid', a code the PDS never sends - the string fallback was
doing all the work. The real wire shape is the GENERIC 'InvalidToken'
code (also used for malformed/expired tokens) with the app-password
specifics only in the message, so the typed path now matches code AND
message ('Bad token scope' / 'Bad token method').

Deactivated and DeactivateAccountDialog used raw switch(e.message)
equality; both now go through the helper. Tests updated to pin the
actual PDS wire shape (and reject plain malformed-token errors).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman 4cbefaf4b2 roast findings: arm ordering, composer deps, follow-all fallthrough, createdAt clobber
Adversarial review (roast) of the branch, 9 path-scoped passes:
- cross-tab rebuild armed the new session's hooks before the deferred
  bundle dispatch, so an 'expired' event fired in that async window
  would be dropped by the reducer's bundle-identity check; arm now
  happens in the same callback as the dispatch, and the dispatched
  account is re-read from the live session (mirrors snapshot-after-
  prep).
- Composer onPressPublish's dep array omitted appviewClient and
  resolveClients (stale-client hazard after cross-tab rebuild).
- follow-all catch blocks (onboarding starter-pack card, starter pack
  screen) showed the error toast but fell through to dereference the
  undefined followUris map, throwing and showing a bogus success toast.
- contacts createProfileRecord unconditionally overwrote the profile's
  createdAt on every run; now preserved when already set, matching
  StepFinished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman 57a6168bbc address claude review: getErrorStatus in feed 429 check, fix reportSubject docs
- PostFeedErrorMessage read error?.status directly; only
  XrpcResponseError carries .status now, so route through
  getErrorStatus() like the rest of detectKnownError (also drops an
  oxlint no-unsafe-member-access suppression).
- reportSubject.ts docblocks contradicted each other about cid
  branding; l.CidString is a plain (unbranded) string alias, so the
  file docstring now says so and the strongRef comments agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman 46095a0220 fix codeql findings: youtube host check and redos-prone url regex
- ExternalPlayer gated WebView navigation with
  event.url.includes('www.youtube.com'), which also matches hostile
  URLs like https://www.youtube.com.evil.com; parse and compare the
  hostname exactly.
- isValidUrlAndDomain used a nested-quantifier regex that backtracks
  exponentially on inputs like '//0.' + '00.'.repeat(n), and it runs on
  every composer keystroke; rewritten as linear-time parsing with the
  same semantics (http/https/ftp/protocol-relative, userinfo, public
  IPv4 or domain host, 2-5 digit port, whitespace-free path). New test
  file pins accept/reject behavior plus ReDoS canaries.

Both pre-existing (2024) issues surfaced by CodeQL on the migration PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman ac2309c182 snapshot session after prep, skip no-op cross-tab rebuilds, restore chat proxy env
Review fixes (PR #11182 round 3):
- The session factories snapshotted the account BEFORE the prep awaits
  (moderation config, age-assurance prefetch) with hooks still disarmed.
  A 401 during prep triggers PasswordSession's internal auto-refresh,
  rotating both tokens; the dropped onUpdated meant the stale refreshJwt
  got persisted (dead on next cold start). The returned account is now
  re-snapshotted after prep, right before arm() - matching the old
  BskyAppAgent.prepare ordering. Tests pin the mid-prep rotation case.
- Cross-tab sync rebuilt and killed the active bundle on ANY saved-
  account change; now bails out when the current account's tokens are
  unchanged.
- buildChatClient hard-coded the SDK's prod chat DID, making the env-
  configurable CHAT_PROXY_DID dead code and routing staging DMs to prod
  chat; new CHAT_PROXY_SERVICE constant restores the override.
- The cross-tab labeler reapply was fire-and-forget; the bundle swap now
  defers until the labeler config resolves, matching the factories.

Note: the reviewer's URL.canParse/ReadableStream claim was verified
INCORRECT - Expo 54's WinterCG runtime defines URL.canParse and Metro
injects a ReadableStream polyfill (expo/virtual/streams.js) into dev and
release bundles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00
Samuel Newman 2f2a28f7f9 import client from lex instead of lex-client 2026-07-30 18:19:43 +03:00
Samuel Newman 2696104e6d survive session-bundle rebuild in chat, feeds, prefs, and age assurance
Review fixes (PR #11182 round 2). Client identity is no longer stable
across token refresh - on web, a cross-tab sync rebuilds the bundle and
disposes the old clients (whose fetch now throws) - so anything that
captured a client at construction time must re-point at the live one:

- MessagesEventBus and Convo get updateClient(); providers sync it via
  effect. No remount, so poll cursors and optimistic pendingMessages
  survive.
- FeedAPI implementations get setClient(); the post-feed queryFn and
  pollLatest re-point page-held apis before fetching (Merge/Home apis
  are stateful across pages, so they cannot be rebuilt per fetch).
- usePreferencesQuery applies fetched labeler dids to the live appview
  client (applyLabelersToClient, factored from session/moderation),
  restoring the old BskyAgent.getPreferences header side effect.
- ageAssurance redirect overlay/dialog polling is mount-only with
  render-synced refs; a client swap mid-poll no longer latches the
  unmounted flag and strands the overlay.
- Convo message-failure classification defers to lex's shouldRetry()
  instead of treating all status-less errors as recoverable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:18:56 +03:00
Samuel Newman 4c2771eca3 fix session refresh persistence, 2fa login, bundle disposal, cross-tab labelers
Review fixes (PR #11182 round 1):
- PasswordSession fires onUpdated/onDeleted BEFORE committing its
  session data; thread the hook payload through to the provider instead
  of reading the live getter. Fixes rotated tokens never persisting
  (eventual forced logout) and expiry not logging out.
- getErrorName now gates on LexError, so LexAuthFactorError (a sibling
  of XrpcError) surfaces AuthFactorTokenRequired and email-2fa users
  get the code input.
- disposeBundle was a no-op; add a kill-switch closure around the
  session's injected fetch (covers the internal auto-refresh path) so a
  replaced session can't consume rotated refresh tokens. kill() also
  disarms the hooks so stale bundles can't dispatch into the reducer.
- cross-tab same-did rebuild now reapplies subscribed labelers to the
  fresh appview client (was built with an empty per-instance set).
- isAppLabeler reads Client.appLabelers instead of the hard-coded prod
  did, restoring test-env and regional-authority classification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:18:56 +03:00
Samuel Newman 564044f6ac trim webpack stats file to assets only
The CI bundle-size-diff action require()s stats.json as one string; the
migration's fine-grained lex/sdk module graph pushed the default stats
output to ~611MB, past V8's 512MB string cap. The action only reads
assets[], so drop the module graph from the generated file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:18:55 +03:00
Samuel Newman 4c0d99158f route appview state and facet resolution off the pds client
Audit of usePdsClient call sites: the PDS client is for com.atproto.*
account/repo operations. Appview-private state (actor/list/thread mutes,
notification seen-state) and mention/facet resolution now use the
appview-routed client. useRichText also regains logged-out mention
resolution via useLexClient's public fallback (usePdsClient throws when
logged out, silently leaving mentions unresolved on StarterPackLanding
and web ProfileHoverCard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:18:55 +03:00
Samuel Newman 8ee62d67bd finish phase 4: drop @atproto/api dependency, lex-only errors, delete toLex and legacy validators
oxlint-suppressions.json gains a baseline entry for webpack.config.js: the
file was never previously staged/linted (lint script covers src+modules
only), and editing it exposed its pre-existing violations to lint-staged.
All suppressed counts are pre-existing; this change only removes a line.
2026-07-30 18:18:38 +03:00
Samuel Newman ca5b8b6346 phase 4: delete the session bridge (SessionAgent, agent.ts, agentToLexClient) 2026-07-30 18:17:39 +03:00
Samuel Newman bab2b621a9 phase 4: migrate account flows, trending queries, notifications, and report subjects off the bridge 2026-07-30 18:17:39 +03:00
Samuel Newman 5a099a8db6 phase 4: migrate ageAssurance unit and chat wrapper signatures to lex clients 2026-07-30 18:16:48 +03:00
Samuel Newman d052d14f2b phase 4 foundations: vendor missing lexicons, refreshSession api, toLex no-op purge, de-agent link-meta 2026-07-30 18:16:48 +03:00
Samuel Newman d13d1ca604 install published sdk preview (bsky-sdk-preview@0.0.1-preview.1) aliased to @bsky.app/sdk 2026-07-30 18:16:02 +03:00
Samuel Newman e7d349bc30 finish phase 3: type-only codemod and zero non-bridge @atproto/api imports
Task 8: codemod repointed 150 namespace-type files to #/lexicons
(AppBskyFeedDefs.PostView -> app.bsky.feed.defs.PostView, .Record -> .Main)
plus ~105 hand-fixed consumers (composer/video state, DebugMod, Profile,
onboarding, dialogs). DM_SERVICE_HEADERS deleted; last chat call sites on
the chat client. RichText.tsx flipped to SDK-only. Remaining @atproto/api
importers: the three session bridge files, the deliberate dual-world
matcher in lib/xrpc-error.ts, and test fixtures. toLex boundary casts are
TODO(phase4)-tagged for bridge removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:15:22 +03:00
Samuel Newman 300a50b69a migrate runtime call sites to lex clients and sdk actions
Phase 3 tasks 3-7 (parallel wave): composer/post pipeline on
pdsClient/appviewClient with structural+instance blob guards and a golden
CID fixture test; chat Convo/EventBus/queries on the dedicated chat client;
preferences sugar to SDK actions on the PDS client; remaining state/queries
producers (usePostThread unspecced flip, video scoped-token clients,
notifications, starter packs, lists) to client.call; UI runtime sweep
(AtUri from @atproto/syntax, moderation fns from @bsky.app/sdk/moderation,
SDK RichText, ozone reason tokens, guard rewrites via #/types/bsky).

Intermediate checkpoint (hooks skipped): ~114 typecheck errors remain in
cross-boundary consumer files, resolved by the type-only codemod next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:12:10 +03:00
Samuel Newman b1a4e1cd16 flip types/bsky sources to #/lexicons, add chat client and strict hook contracts
Phase 3 task 2: types/bsky sibling modules (post/profile/starterPack) now
source from generated #/lexicons with interim dual-world widening aliases
(TODO(phase4) markers). SessionBundle gains a chatClient proxied to
did:web:api.bsky.chat#bsky_chat via useChatClient(). usePdsClient() and
useChatClient() no longer fall back to the public appview when logged out -
they return a client that throws NotAuthenticatedError before any network
I/O; useMaybePdsClient()/useMaybeChatClient() cover logged-out-aware
callers. RichText pilot: useRichText.ts on @bsky.app/sdk/richtext with
detectFacets(pdsClient); RichText.tsx display sink accepts both worlds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:07:26 +03:00
Samuel Newman e6f73fc110 add dual-world xrpc error helpers and toLex migration escape hatch
Phase 3 foundations (task 1): src/lib/xrpc-error.ts matches both the old
@atproto/api XRPCError and lex-client XrpcError/XrpcResponseError during
the migration; errors.ts token-invalid matching goes through it. toLex<T>()
added to #/types/bsky as a marked interim cast for mixed-world boundaries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:07:26 +03:00
Samuel Newman bf8b6bc0c7 rework session layer on PasswordSession + three-client pattern
- PasswordSession (@atproto/lex-password-session) is now the single auth
  core; SessionAgent bridges it to the base Agent from @atproto/api so
  the ~148 useAgent() consumer files keep working unchanged
- SessionBundle carries {session, agent, accountClient, appviewClient};
  useLexClient() now returns the authed appview client, new
  useAppviewClient()/usePdsClient() hooks
- reducer: OpaqueSessionBundle, new replaced-current-bundle action for
  network-free cross-tab same-did sync (PasswordSession is immutable, so
  the bundle is rebuilt instead of mutated in place)
- moderation: AtpAgent.configure -> Client.configure + Agent.configure,
  per-account labelers on both the bridge and the appview client
- push-token unregistration uses temporary PasswordSessions
- hard-tail migrations: SignupQueued refresh via resumeSession shim,
  useBeginAgeAssurance scoped-token raw Client, useAccountEmailState
  reads currentAccount
- PDS routing preserved on the no-network resume fast path by
  synthesizing a minimal didDoc from the persisted pdsUrl

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:06:58 +03:00
Samuel Newman 105e68c12c add lex client seam, types/bsky schema helpers, pilot trending migration 2026-07-30 18:06:12 +03:00
Samuel Newman 15e232749a add @bsky.app/sdk + @atproto/lex, vendor lexicons, codegen via lex cli 2026-07-30 18:05:26 +03:00
Samuel Newman 17c1ee4869 Handle PR OTA version mismatches and failures (#11330) 2026-07-30 07:40:49 -07:00
Oleksii Bulenok 157a1893fd fix TextInput resizes on Android when the first chat is typed (#11318) 2026-07-30 12:53:35 +02:00
Oleksii Bulenok cc3c01267d Fix alt text input field not growing more than 3 lines (#11328) 2026-07-30 12:53:09 +02:00
pfrazee 973d0b06f2 Nightly source-language update 2026-07-30 03:10:31 +00:00
DS Boyce 0be8b1dd93 Add a dialog for debugging feature flags to developer settings (#10988) 2026-07-29 16:49:32 -07:00
DS Boyce 91517af01c Log rank and parent index with trending topic events (#11331) 2026-07-29 16:14:44 -07:00
DS Boyce 15c6c7955d Get number of trending topics for Explore from GrowthBook (#11324) 2026-07-29 12:12:15 -07:00
Samuel Newman 904097e8c1 Fix peek menu crash (#11315) 2026-07-29 22:11:42 +03:00