about summary refs log tree commit diff
path: root/src/state/queries
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-07-17 13:22:09 -0500
committerGitHub <noreply@github.com>2025-07-17 13:22:09 -0500
commit00b017804bcb811b5f9292a88619423df3a29ef8 (patch)
tree835c1d2d66c9d8473f3e48b49b43e071876575c8 /src/state/queries
parentd13a2e5f69fac4a4a5a89692362b827b8f0223e4 (diff)
downloadvoidsky-00b017804bcb811b5f9292a88619423df3a29ef8.tar.zst
Fix bug where replying to grandparents re-orders thread (#8662)
* Fix bug where optimistic replies were inserted above the root post

* Rename variables for clarity
Diffstat (limited to 'src/state/queries')
-rw-r--r--src/state/queries/usePostThread/queryCache.ts49
-rw-r--r--src/state/queries/usePostThread/utils.ts6
2 files changed, 32 insertions, 23 deletions
diff --git a/src/state/queries/usePostThread/queryCache.ts b/src/state/queries/usePostThread/queryCache.ts
index 871033395..826932349 100644
--- a/src/state/queries/usePostThread/queryCache.ts
+++ b/src/state/queries/usePostThread/queryCache.ts
@@ -77,53 +77,56 @@ export function createCacheMutator({
 
       function mutator<T>(thread: ApiThreadItem[]): T[] {
         for (let i = 0; i < thread.length; i++) {
-          const existingParent = thread[i]
-          if (!AppBskyUnspeccedDefs.isThreadItemPost(existingParent.value))
-            continue
-          if (existingParent.uri !== parentUri) continue
+          const parent = thread[i]
+
+          if (!AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) continue
+          if (parent.uri !== parentUri) continue
 
           /*
            * Update parent data
            */
-          existingParent.value.post = {
-            ...existingParent.value.post,
-            replyCount: (existingParent.value.post.replyCount || 0) + 1,
+          parent.value.post = {
+            ...parent.value.post,
+            replyCount: (parent.value.post.replyCount || 0) + 1,
           }
 
-          const opDid = getRootPostAtUri(existingParent.value.post)?.host
-          const nextItem = thread.at(i + 1)
-          const isReplyToRoot = existingParent.depth === 0
+          const opDid = getRootPostAtUri(parent.value.post)?.host
+          const nextPreexistingItem = thread.at(i + 1)
           const isEndOfReplyChain =
-            !nextItem || nextItem.depth <= existingParent.depth
-          const firstReply = replies.at(0)
+            !nextPreexistingItem || nextPreexistingItem.depth <= parent.depth
+          const isParentRoot = parent.depth === 0
+          const isParentBelowRoot = parent.depth > 0
+          const optimisticReply = replies.at(0)
           const opIsReplier = AppBskyUnspeccedDefs.isThreadItemPost(
-            firstReply?.value,
+            optimisticReply?.value,
           )
-            ? opDid === firstReply.value.post.author.did
+            ? opDid === optimisticReply.value.post.author.did
             : false
 
           /*
-           * Always insert replies if the following conditions are met.
+           * Always insert replies if the following conditions are met. Max
+           * depth checks are handled below.
            */
-          const shouldAlwaysInsertReplies =
-            isReplyToRoot ||
-            params.view === 'tree' ||
+          const canAlwaysInsertReplies =
+            isParentRoot ||
+            (params.view === 'tree' && isParentBelowRoot) ||
             (params.view === 'linear' && isEndOfReplyChain)
           /*
-           * Maybe insert replies if the replier is the OP and certain conditions are met
+           * Maybe insert replies if we're in linear view, the replier is the
+           * OP, and certain conditions are met
            */
           const shouldReplaceWithOPReplies =
-            !isReplyToRoot && params.view === 'linear' && opIsReplier
+            params.view === 'linear' && opIsReplier && isParentBelowRoot
 
-          if (shouldAlwaysInsertReplies || shouldReplaceWithOPReplies) {
-            const branch = getBranch(thread, i, existingParent.depth)
+          if (canAlwaysInsertReplies || shouldReplaceWithOPReplies) {
+            const branch = getBranch(thread, i, parent.depth)
             /*
              * OP insertions replace other replies _in linear view_.
              */
             const itemsToRemove = shouldReplaceWithOPReplies ? branch.length : 0
             const itemsToInsert = replies
               .map((r, ri) => {
-                r.depth = existingParent.depth + 1 + ri
+                r.depth = parent.depth + 1 + ri
                 return r
               })
               .filter(r => {
diff --git a/src/state/queries/usePostThread/utils.ts b/src/state/queries/usePostThread/utils.ts
index b8ab340d8..265bf7f5f 100644
--- a/src/state/queries/usePostThread/utils.ts
+++ b/src/state/queries/usePostThread/utils.ts
@@ -33,6 +33,12 @@ export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) {
       AppBskyFeedPost.isRecord,
     )
   ) {
+    /**
+     * If the record has no `reply` field, it is a root post.
+     */
+    if (!post.record.reply) {
+      return new AtUri(post.uri)
+    }
     if (post.record.reply?.root?.uri) {
       return new AtUri(post.record.reply.root.uri)
     }