about summary refs log tree commit diff
path: root/src/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/api')
-rw-r--r--src/lib/api/feed-manip.ts45
-rw-r--r--src/lib/api/feed/author.ts28
-rw-r--r--src/lib/api/feed/custom.ts28
-rw-r--r--src/lib/api/feed/following.ts25
-rw-r--r--src/lib/api/feed/likes.ts28
-rw-r--r--src/lib/api/feed/list.ts28
-rw-r--r--src/lib/api/feed/merge.ts82
-rw-r--r--src/lib/api/feed/types.ts21
-rw-r--r--src/lib/api/index.ts48
9 files changed, 160 insertions, 173 deletions
diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts
index 8f259a910..1123c4e23 100644
--- a/src/lib/api/feed-manip.ts
+++ b/src/lib/api/feed-manip.ts
@@ -4,7 +4,7 @@ import {
   AppBskyEmbedRecordWithMedia,
   AppBskyEmbedRecord,
 } from '@atproto/api'
-import {FeedSourceInfo} from './feed/types'
+import {ReasonFeedSource} from './feed/types'
 import {isPostInLanguage} from '../../locale/helpers'
 type FeedViewPost = AppBskyFeedDefs.FeedViewPost
 
@@ -16,13 +16,7 @@ export type FeedTunerFn = (
 export class FeedViewPostsSlice {
   isFlattenedReply = false
 
-  constructor(public items: FeedViewPost[] = []) {}
-
-  get _reactKey() {
-    return `slice-${this.items[0].post.uri}-${
-      this.items[0].reason?.indexedAt || this.items[0].post.indexedAt
-    }`
-  }
+  constructor(public items: FeedViewPost[], public _reactKey: string) {}
 
   get uri() {
     if (this.isFlattenedReply) {
@@ -65,9 +59,9 @@ export class FeedViewPostsSlice {
     )
   }
 
-  get source(): FeedSourceInfo | undefined {
+  get source(): ReasonFeedSource | undefined {
     return this.items.find(item => '__source' in item && !!item.__source)
-      ?.__source as FeedSourceInfo
+      ?.__source as ReasonFeedSource
   }
 
   containsUri(uri: string) {
@@ -116,18 +110,35 @@ export class FeedViewPostsSlice {
   }
 }
 
+export class NoopFeedTuner {
+  private keyCounter = 0
+
+  reset() {
+    this.keyCounter = 0
+  }
+  tune(
+    feed: FeedViewPost[],
+    _opts?: {dryRun: boolean; maintainOrder: boolean},
+  ): FeedViewPostsSlice[] {
+    return feed.map(
+      item => new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+    )
+  }
+}
+
 export class FeedTuner {
+  private keyCounter = 0
   seenUris: Set<string> = new Set()
 
-  constructor() {}
+  constructor(public tunerFns: FeedTunerFn[]) {}
 
   reset() {
+    this.keyCounter = 0
     this.seenUris.clear()
   }
 
   tune(
     feed: FeedViewPost[],
-    tunerFns: FeedTunerFn[] = [],
     {dryRun, maintainOrder}: {dryRun: boolean; maintainOrder: boolean} = {
       dryRun: false,
       maintainOrder: false,
@@ -136,7 +147,9 @@ export class FeedTuner {
     let slices: FeedViewPostsSlice[] = []
 
     if (maintainOrder) {
-      slices = feed.map(item => new FeedViewPostsSlice([item]))
+      slices = feed.map(
+        item => new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+      )
     } else {
       // arrange the posts into thread slices
       for (let i = feed.length - 1; i >= 0; i--) {
@@ -152,12 +165,14 @@ export class FeedTuner {
             continue
           }
         }
-        slices.unshift(new FeedViewPostsSlice([item]))
+        slices.unshift(
+          new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+        )
       }
     }
 
     // run the custom tuners
-    for (const tunerFn of tunerFns) {
+    for (const tunerFn of this.tunerFns) {
       slices = tunerFn(this, slices.slice())
     }
 
diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts
index ec8795e1a..92df84f8b 100644
--- a/src/lib/api/feed/author.ts
+++ b/src/lib/api/feed/author.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetAuthorFeed as GetAuthorFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class AuthorFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetAuthorFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetAuthorFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getAuthorFeed({
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getAuthorFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: this._filter(res.data.feed),
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index d05d5acd6..47ffc65ed 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetFeed as GetCustomFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class CustomFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetCustomFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetCustomFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       // NOTE
       // some custom feeds fail to enforce the pagination limit
       // so we manually truncate here
diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts
index f14807a57..24389b5ed 100644
--- a/src/lib/api/feed/following.ts
+++ b/src/lib/api/feed/following.ts
@@ -1,30 +1,29 @@
 import {AppBskyFeedDefs} from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class FollowingFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(public rootStore: RootStoreModel) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor() {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getTimeline({
-      cursor: this.cursor,
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getTimeline({
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts
index e9bb14b0b..2b0afdf11 100644
--- a/src/lib/api/feed/likes.ts
+++ b/src/lib/api/feed/likes.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetActorLikes as GetActorLikes,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class LikesFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetActorLikes.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetActorLikes.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getActorLikes({
+    const res = await getAgent().getActorLikes({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.getActorLikes({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().getActorLikes({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts
index e58494675..19f2ff177 100644
--- a/src/lib/api/feed/list.ts
+++ b/src/lib/api/feed/list.ts
@@ -2,37 +2,33 @@ import {
   AppBskyFeedDefs,
   AppBskyFeedGetListFeed as GetListFeed,
 } from '@atproto/api'
-import {RootStoreModel} from 'state/index'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class ListFeedAPI implements FeedAPI {
-  cursor: string | undefined
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: GetListFeed.QueryParams,
-  ) {}
-
-  reset() {
-    this.cursor = undefined
-  }
+  constructor(public params: GetListFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
-      cursor: this.cursor,
+      cursor,
       limit,
     })
     if (res.success) {
-      this.cursor = res.data.cursor
       return {
         cursor: res.data.cursor,
         feed: res.data.feed,
diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts
index e0fbcecd8..11e963f0a 100644
--- a/src/lib/api/feed/merge.ts
+++ b/src/lib/api/feed/merge.ts
@@ -1,11 +1,13 @@
 import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api'
 import shuffle from 'lodash.shuffle'
-import {RootStoreModel} from 'state/index'
 import {timeout} from 'lib/async/timeout'
 import {bundleAsync} from 'lib/async/bundle'
 import {feedUriToHref} from 'lib/strings/url-helpers'
 import {FeedTuner} from '../feed-manip'
-import {FeedAPI, FeedAPIResponse, FeedSourceInfo} from './types'
+import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
+import {FeedParams} from '#/state/queries/post-feed'
+import {FeedTunerFn} from '../feed-manip'
+import {getAgent} from '#/state/session'
 
 const REQUEST_WAIT_MS = 500 // 500ms
 const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
@@ -17,28 +19,44 @@ export class MergeFeedAPI implements FeedAPI {
   itemCursor = 0
   sampleCursor = 0
 
-  constructor(public rootStore: RootStoreModel) {
-    this.following = new MergeFeedSource_Following(this.rootStore)
+  constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) {
+    this.following = new MergeFeedSource_Following(this.feedTuners)
   }
 
   reset() {
-    this.following = new MergeFeedSource_Following(this.rootStore)
+    this.following = new MergeFeedSource_Following(this.feedTuners)
     this.customFeeds = [] // just empty the array, they will be captured in _fetchNext()
     this.feedCursor = 0
     this.itemCursor = 0
     this.sampleCursor = 0
+    if (this.params.mergeFeedEnabled && this.params.mergeFeedSources) {
+      this.customFeeds = shuffle(
+        this.params.mergeFeedSources.map(
+          feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners),
+        ),
+      )
+    } else {
+      this.customFeeds = []
+    }
   }
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.rootStore.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
   }
 
-  async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
-    // we capture here to ensure the data has loaded
-    this._captureFeedsIfNeeded()
+  async fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse> {
+    if (!cursor) {
+      this.reset()
+    }
 
     const promises = []
 
@@ -76,7 +94,7 @@ export class MergeFeedAPI implements FeedAPI {
     }
 
     return {
-      cursor: posts.length ? 'fake' : undefined,
+      cursor: posts.length ? String(this.itemCursor) : undefined,
       feed: posts,
     }
   }
@@ -107,28 +125,15 @@ export class MergeFeedAPI implements FeedAPI {
     // provide follow
     return this.following.take(1)
   }
-
-  _captureFeedsIfNeeded() {
-    if (!this.rootStore.preferences.homeFeed.lab_mergeFeedEnabled) {
-      return
-    }
-    if (this.customFeeds.length === 0) {
-      this.customFeeds = shuffle(
-        this.rootStore.preferences.savedFeeds.map(
-          feedUri => new MergeFeedSource_Custom(this.rootStore, feedUri),
-        ),
-      )
-    }
-  }
 }
 
 class MergeFeedSource {
-  sourceInfo: FeedSourceInfo | undefined
+  sourceInfo: ReasonFeedSource | undefined
   cursor: string | undefined = undefined
   queue: AppBskyFeedDefs.FeedViewPost[] = []
   hasMore = true
 
-  constructor(public rootStore: RootStoreModel) {}
+  constructor(public feedTuners: FeedTunerFn[]) {}
 
   get numReady() {
     return this.queue.length
@@ -175,7 +180,7 @@ class MergeFeedSource {
 }
 
 class MergeFeedSource_Following extends MergeFeedSource {
-  tuner = new FeedTuner()
+  tuner = new FeedTuner(this.feedTuners)
 
   reset() {
     super.reset()
@@ -190,16 +195,12 @@ class MergeFeedSource_Following extends MergeFeedSource {
     cursor: string | undefined,
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
-    const res = await this.rootStore.agent.getTimeline({cursor, limit})
+    const res = await getAgent().getTimeline({cursor, limit})
     // run the tuner pre-emptively to ensure better mixing
-    const slices = this.tuner.tune(
-      res.data.feed,
-      this.rootStore.preferences.getFeedTuners('home'),
-      {
-        dryRun: false,
-        maintainOrder: true,
-      },
-    )
+    const slices = this.tuner.tune(res.data.feed, {
+      dryRun: false,
+      maintainOrder: true,
+    })
     res.data.feed = slices.map(slice => slice.rootItem)
     return res
   }
@@ -208,15 +209,16 @@ class MergeFeedSource_Following extends MergeFeedSource {
 class MergeFeedSource_Custom extends MergeFeedSource {
   minDate: Date
 
-  constructor(public rootStore: RootStoreModel, public feedUri: string) {
-    super(rootStore)
+  constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) {
+    super(feedTuners)
     this.sourceInfo = {
+      $type: 'reasonFeedSource',
       displayName: feedUri.split('/').pop() || '',
       uri: feedUriToHref(feedUri),
     }
     this.minDate = new Date(Date.now() - POST_AGE_CUTOFF)
-    this.rootStore.agent.app.bsky.feed
-      .getFeedGenerator({
+    getAgent()
+      .app.bsky.feed.getFeedGenerator({
         feed: feedUri,
       })
       .then(
@@ -234,7 +236,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
     try {
-      const res = await this.rootStore.agent.app.bsky.feed.getFeed({
+      const res = await getAgent().app.bsky.feed.getFeed({
         cursor,
         limit,
         feed: this.feedUri,
diff --git a/src/lib/api/feed/types.ts b/src/lib/api/feed/types.ts
index 006344334..5d2a90c1d 100644
--- a/src/lib/api/feed/types.ts
+++ b/src/lib/api/feed/types.ts
@@ -6,12 +6,27 @@ export interface FeedAPIResponse {
 }
 
 export interface FeedAPI {
-  reset(): void
   peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost>
-  fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse>
+  fetch({
+    cursor,
+    limit,
+  }: {
+    cursor: string | undefined
+    limit: number
+  }): Promise<FeedAPIResponse>
 }
 
-export interface FeedSourceInfo {
+export interface ReasonFeedSource {
+  $type: 'reasonFeedSource'
   uri: string
   displayName: string
 }
+
+export function isReasonFeedSource(v: unknown): v is ReasonFeedSource {
+  return (
+    !!v &&
+    typeof v === 'object' &&
+    '$type' in v &&
+    v.$type === 'reasonFeedSource'
+  )
+}
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 9d48a78c0..a78abcacd 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -4,12 +4,12 @@ import {
   AppBskyEmbedRecord,
   AppBskyEmbedRecordWithMedia,
   AppBskyRichtextFacet,
+  BskyAgent,
   ComAtprotoLabelDefs,
   ComAtprotoRepoUploadBlob,
   RichText,
 } from '@atproto/api'
 import {AtUri} from '@atproto/api'
-import {RootStoreModel} from 'state/models/root-store'
 import {isNetworkError} from 'lib/strings/errors'
 import {LinkMeta} from '../link-meta/link-meta'
 import {isWeb} from 'platform/detection'
@@ -25,46 +25,19 @@ export interface ExternalEmbedDraft {
   localThumb?: ImageModel
 }
 
-export async function resolveName(store: RootStoreModel, didOrHandle: string) {
-  if (!didOrHandle) {
-    throw new Error('Invalid handle: ""')
-  }
-  if (didOrHandle.startsWith('did:')) {
-    return didOrHandle
-  }
-
-  // we run the resolution always to ensure freshness
-  const promise = store.agent
-    .resolveHandle({
-      handle: didOrHandle,
-    })
-    .then(res => {
-      store.handleResolutions.cache.set(didOrHandle, res.data.did)
-      return res.data.did
-    })
-
-  // but we can return immediately if it's cached
-  const cached = store.handleResolutions.cache.get(didOrHandle)
-  if (cached) {
-    return cached
-  }
-
-  return promise
-}
-
 export async function uploadBlob(
-  store: RootStoreModel,
+  agent: BskyAgent,
   blob: string,
   encoding: string,
 ): Promise<ComAtprotoRepoUploadBlob.Response> {
   if (isWeb) {
     // `blob` should be a data uri
-    return store.agent.uploadBlob(convertDataURIToUint8Array(blob), {
+    return agent.uploadBlob(convertDataURIToUint8Array(blob), {
       encoding,
     })
   } else {
     // `blob` should be a path to a file in the local FS
-    return store.agent.uploadBlob(
+    return agent.uploadBlob(
       blob, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
       {encoding},
     )
@@ -81,12 +54,11 @@ interface PostOpts {
   extLink?: ExternalEmbedDraft
   images?: ImageModel[]
   labels?: string[]
-  knownHandles?: Set<string>
   onStateChange?: (state: string) => void
   langs?: string[]
 }
 
-export async function post(store: RootStoreModel, opts: PostOpts) {
+export async function post(agent: BskyAgent, opts: PostOpts) {
   let embed:
     | AppBskyEmbedImages.Main
     | AppBskyEmbedExternal.Main
@@ -102,7 +74,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
   )
 
   opts.onStateChange?.('Processing...')
-  await rt.detectFacets(store.agent)
+  await rt.detectFacets(agent)
   rt = shortenLinks(rt)
 
   // filter out any mention facets that didn't map to a user
@@ -135,7 +107,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
       await image.compress()
       const path = image.compressed?.path ?? image.path
       const {width, height} = image.compressed || image
-      const res = await uploadBlob(store, path, 'image/jpeg')
+      const res = await uploadBlob(agent, path, 'image/jpeg')
       images.push({
         image: res.data.blob,
         alt: image.altText ?? '',
@@ -185,7 +157,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
         }
         if (encoding) {
           const thumbUploadRes = await uploadBlob(
-            store,
+            agent,
             opts.extLink.localThumb.path,
             encoding,
           )
@@ -224,7 +196,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
   // add replyTo if post is a reply to another post
   if (opts.replyTo) {
     const replyToUrip = new AtUri(opts.replyTo)
-    const parentPost = await store.agent.getPost({
+    const parentPost = await agent.getPost({
       repo: replyToUrip.host,
       rkey: replyToUrip.rkey,
     })
@@ -257,7 +229,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
 
   try {
     opts.onStateChange?.('Posting...')
-    return await store.agent.post({
+    return await agent.post({
       text: rt.text,
       facets: rt.facets,
       reply,