Skip to content

fix(feed): deleting a post from a list no longer pops the feed - #3410

Merged
feruzm merged 6 commits into
developmentfrom
fix/feed-post-delete
Aug 4, 2026
Merged

fix(feed): deleting a post from a list no longer pops the feed#3410
feruzm merged 6 commits into
developmentfrom
fix/feed-post-delete

Conversation

@feruzm

@feruzm feruzm commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes #3407

postsListContainer mounted the options sheet with no onDelete, so deleting a post from a feed ran the sheet's own path: navigation.goBack() popped the feed screen, and nothing removed the post, so it stayed visible until something refetched. Reachable on your own profile feed, where _canDeletePost is satisfied.

Where the removal belongs

The container receives posts as a prop and does not own the list, so it cannot remove anything itself. The owner is useFeedQuery, which is where deletePost now lives, modelled on wavesQueries.deleteWave:

  • broadcast the delete
  • prune the post from that feed's infinite-query cache, matching author as well as permlink since a permlink is only unique per author
  • toast

Wired through as onDeletePost: postsTabContent -> postsListContainer -> the sheet's onDelete.

The cache is patched, not invalidated. The delete broadcasts async, so mutateAsync resolves on mempool acceptance and a refetch issued at that point returns pre-transaction state, bringing the post straight back. Same reason deleteWave patches, and the same trap as the setRole cache work in #3397.

Consumer audit

This is the last list consumer. I checked what each screen actually renders this time, not just which pass the prop, since that narrower check is what let the #3408 regression through.

Consumer onDelete Verdict
wavesScreen yes delegates to wavesQuery.deleteWave
commentsView yes #3405
postComments yes #3406
postsListContainer yes this PR
postScreen no correct - renders the post or comment as primary content, so the pop is right
editorScreen no correct - the content being edited

Every consumer owning a surrounding list now delegates, and the fallback is only reached where popping is genuinely correct.

Deliberately not in this PR

The sheet still pops by default, which is the footgun behind all three occurrences. Inverting it to an explicit opt-in is filed as #3409 rather than bundled here: there is no active bug once this lands, and the last attempt to harden that default inside a bug-fix PR (#3408's parent_author gate) caused a regression. It deserves its own change and its own review.

Testing

  • yarn test:ci: 730 passed, 1 skipped, 49 suites.
  • yarn lint: 0 errors.

Device checks:

  • On your own profile feed, delete an eligible post from the options sheet. It should disappear from the list and you should stay on the feed.
  • Confirm it does not reappear on scroll or tab switch, which is what an invalidate-instead-of-patch would have caused.
  • Confirm deleting a post from its own screen still navigates back, unchanged.

Summary by CodeRabbit

  • New Features

    • Added the ability to delete posts directly from feed views.
    • Feed content updates automatically after a post is removed, including previously loaded results.
    • A localized status notification is displayed after successful deletion.
    • Deletion validates account access and the selected post before processing.
  • Bug Fixes

    • Prevented deletion of original posts represented by cross-post content.

postsListContainer mounted the options sheet with no onDelete, so
deleting a post from a feed ran the sheet's own path: navigation.goBack()
popped the feed screen, and nothing removed the post, so it stayed
visible until something refetched.

The container receives posts as a prop and does not own the list, so the
removal belongs with the query that does. Adds deletePost to useFeedQuery,
modelled on wavesQueries.deleteWave: broadcast, then prune the post from
that feed's infinite-query cache, matching author as well as permlink
since a permlink is only unique per author.

The cache is patched rather than invalidated because the delete
broadcasts async, so a refetch issued at that point returns
pre-transaction state and would bring the post straight back.

Wired through postsTabContent and postsListContainer as onDeletePost.

Every PostOptionsModal consumer that owns a surrounding list now
delegates. The two that do not, postScreen and editorScreen, render the
content as the screen itself, so the fallback pop is correct for them.
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown

Greptile Summary

The PR delegates feed deletion to useFeedQuery, removes successfully deleted posts from the infinite-query cache, and prevents parsed cross-post cards from targeting their original posts.

  • Wires the feed-owned delete callback through the tab and list components into the options modal.
  • Filters session-deleted identities from assembled feed data, including separately queried pinned posts.
  • Disables deletion for parsed cross-post content.

Confidence Score: 4/5

The PR is not yet safe to merge because deleting a pinned post can still be visually undone by restarting the app before the backend reflects the asynchronous deletion.

The current implementation stores deleted identities only in module memory; after a process restart, the independently fetched pinned-post query can return and render the stale post because no durable suppression state remains.

Files Needing Attention: src/providers/queries/postQueries/feedQueries.ts

Important Files Changed

Filename Overview
src/providers/queries/postQueries/feedQueries.ts Adds feed-owned deletion and cache/display pruning, but the pinned-post suppression remains process-local and can be lost before remote indexing catches up.
src/components/postOptionsModal/container/postOptionsModal.tsx Prevents parsed cross-post cards from exposing deletion that would otherwise target the original post.
src/components/postsList/container/postsListContainer.tsx Forwards the list owner's optional deletion handler to the post options modal.
src/components/tabbedPosts/view/postsTabContent.tsx Connects the feed query's deletion callback to the rendered posts list.

Sequence Diagram

sequenceDiagram
  participant User
  participant Modal as PostOptionsModal
  participant Feed as useFeedQuery
  participant Chain as Hive mutation
  participant Cache as React Query cache
  User->>Modal: Delete post
  Modal->>Feed: onDelete(content)
  Feed->>Chain: Broadcast async deletion
  Chain-->>Feed: Mempool acceptance
  Feed->>Cache: Prune author/permlink
  Feed->>Feed: Record deleted identity
  Feed-->>User: Updated feed and toast
Loading

Reviews (6): Last reviewed commit: "fix(post): do not offer delete on a cros..." | Re-trigger Greptile

Comment thread src/providers/queries/postQueries/feedQueries.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04fae0a031

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// Match author as well as permlink: a permlink is only unique per author,
// so filtering on it alone could drop someone else's post.
queryClient.setQueryData<InfiniteData<any[]>>(queryOptions.queryKey, (oldData) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove deleted pinned posts from the pinned query

When an eligible pinned post is deleted from the user's profile, this only patches the infinite-feed cache, but the displayed list is assembled with pinnedPostQuery.data ahead of those pages at lines 187–190. Consequently the successful deletion and toast leave the pinned post visible until that separate post query happens to refetch; clear or update the pinned query when it identifies the deleted post.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a19af5c. You are right: _data prepends pinnedPostQuery.data via unionBy before the feed pages, so patching the infinite-feed cache alone left a deleted pinned post on screen.

Rather than patch a second cache, the fix records the deleted post's author/permlink and applies it where the list is assembled, next to the existing mute filter. That is the displayed identity, so one rule covers the pinned post, an ordinary feed row, and the cross-post case raised separately on this PR.

The cache patch stays alongside it: it is what makes the removal durable across a remount, while this covers what that cache cannot express.

Comment on lines +233 to +236
pages: oldData.pages.map((page) =>
page.filter(
(post) =>
!(post?.author === currentAccount.name && post?.permlink === content.permlink),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prune cross-post wrappers using the displayed original

When the current user's deletable post is displayed through another author's cross-post, parsePost replaces the rendered author and permlink with original_entry values, so the menu deletes the original; however, this callback filters the raw query page, where those top-level fields still identify the cross-post wrapper. The predicate therefore retains the item and the deleted post remains visible, so it must also match the wrapper's original_entry identity.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a19af5c. This one was more subtle than it looks and you read it correctly.

select does parsePost({ ...post }, ...) on a shallow copy specifically so it does not mutate the cache, which means the cache holds raw posts while the rendered list holds parsed ones. For a cross-post parsePost swaps author and permlink to the original_entry, so the sheet acts on the original while the cached row still carries the wrapper's identity. The predicate matched neither, exactly as you describe.

Fixed by filtering on the displayed identity instead of trying to reconcile the two shapes: the deleted author/permlink is recorded and applied where the list is assembled. Verified the predicate against all the cases, including that a same-permlink-different-author post survives:

plain deleted post kept?    false   (want false)
pinned deleted post kept?   false   (want false)
crosspost row (parsed=orig) false   (want false)
other user's post kept?     true    (want true)
same permlink diff author   true    (want true)

One nuance worth stating rather than hiding: the wrapper is @bob's own post and still exists on chain, so a refetch can legitimately bring that row back showing a deleted original. Removing it here matches what the user just acted on, but the underlying oddity is that delete-post is offered on a cross-post card at all. Mute and pin are already withheld there (_canModerateCommunityPost) precisely because the identity is ambiguous. Extending that to delete is arguably the better answer, but it changes which actions are offered on the post detail screen too, so I have not folded it into this PR.

…e list

Two review findings, both cases the cache patch alone could not reach.

The pinned post comes from its own query and is prepended to the feed
data, so patching the infinite-feed cache left a deleted pinned post
visible. And select() parses a shallow copy, so the cache holds raw
posts: for a cross-post the cached author and permlink are the wrapper's
while the rendered ones are the original's, and the predicate matched
neither.

Records the author/permlink of posts deleted this session and applies it
where the list is assembled, alongside the existing mute filter. That is
the displayed identity, which is what the user acted on, so it covers a
plain feed row, a pinned post and a cross-post row with one rule.

The cache patch stays: it is what keeps the removal durable across a
remount, while this covers what the cache cannot express.

Author is matched as well as permlink throughout, since a permlink is
only unique per author.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a900718a-3657-4985-bbf1-674c2c415549

📥 Commits

Reviewing files that changed from the base of the PR and between 1091643 and c14088d.

📒 Files selected for processing (1)
  • src/components/postOptionsModal/container/postOptionsModal.tsx

📝 Walkthrough

Walkthrough

The feed query now exposes post deletion. The posts list passes the callback to PostOptionsModal, and PostsTabContent supplies the feed query implementation. Deletion updates cached feed pages and excludes deleted identities, while cross-post deletion is disabled.

Changes

Feed post deletion

Layer / File(s) Summary
Feed deletion implementation
src/providers/queries/postQueries/feedQueries.ts
useFeedQuery validates and deletes posts through the SDK, filters deleted identities, updates cached feed pages, and dispatches a localized removal notification.
List deletion callback wiring
src/components/postsList/container/postsListContainer.tsx, src/components/tabbedPosts/view/postsTabContent.tsx
PostsList accepts onDeletePost, passes it to PostOptionsModal, and receives feedQuery.deletePost from PostsTabContent.
Cross-post deletion guard
src/components/postOptionsModal/container/postOptionsModal.tsx
PostOptionsModal disables deletion for cross-post content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PostOptionsModal
  participant PostsList
  participant useFeedQuery
  participant SDK
  participant FeedCache
  PostOptionsModal->>PostsList: invoke onDeletePost(content)
  PostsList->>useFeedQuery: call deletePost(content)
  useFeedQuery->>SDK: submit deletion
  SDK-->>useFeedQuery: return deletion result
  useFeedQuery->>FeedCache: remove matching author/permlink
Loading

Possibly related issues

Possibly related PRs

Poem

A rabbit wires the delete call,
The feed removes the post from all.
Cross-posts stay safely in their place,
While cache and notice keep their pace.
— 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary fix: deleting a feed-list post no longer pops the feed.
Linked Issues check ✅ Passed The changes add list-specific deletion handling, remove matching posts from feed caches, and avoid the fallback navigation behavior required by issue #3407.
Out of Scope Changes check ✅ Passed The cross-post deletion safeguard is directly related to preventing deletion of the wrong content and does not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/feed-post-delete

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/providers/queries/postQueries/feedQueries.ts`:
- Around line 227-240: Update the pages mapping in the queryClient.setQueryData
callback to check Array.isArray(page) before calling filter, returning non-array
pages unchanged. Keep the existing post author/permlink filtering behavior for
array pages and match the guard used by the hook’s select callback.
- Around line 218-242: Wrap the sdkDeleteMutation.mutateAsync call in the
deletePost flow with error handling so broadcast failures do not proceed to the
cache update or success toast. On failure, dispatch an appropriate error toast
using the existing notification and intl patterns, while preserving the current
success path after a successful mutation.
- Around line 212-216: Update deletePost in the pinned-post deletion path to
also clear or invalidate pinnedPostQuery’s cache for the same author and
permlink when content.permlink matches pinnedPermlink, before the cached result
is rebuilt. Preserve the existing feed infinite-query pruning and ensure _data
cannot re-add the deleted pinned post.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e11c2f1-8205-42db-b444-166a2f50cce1

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff851a and 04fae0a.

📒 Files selected for processing (3)
  • src/components/postsList/container/postsListContainer.tsx
  • src/components/tabbedPosts/view/postsTabContent.tsx
  • src/providers/queries/postQueries/feedQueries.ts

Comment thread src/providers/queries/postQueries/feedQueries.ts
Comment thread src/providers/queries/postQueries/feedQueries.ts
Comment thread src/providers/queries/postQueries/feedQueries.ts
select() in this hook already treats a page as possibly not an array. The
cache patch filtered unconditionally, so the same case would throw and
abort the whole update rather than skipping one page. Matches the guard
the hook already applies.
Comment thread src/providers/queries/postQueries/feedQueries.ts Outdated
feruzm added 2 commits August 2, 2026 11:49
deletedKeys is component state, so it only hides the pinned post for the
life of the hook. Leaving the profile and returning remounts with an
empty set while pinnedPostQuery still holds the post, and the union
prepends it again.

The feed cache patch did not cover this either: the pinned post is served
from its own query and was never in the feed cache to begin with, so my
earlier claim that the patch made the removal durable was only true for
ordinary feed rows.

Clears that query when the deleted post is the pinned one. The key is
built with the same getPostQueryOptions call useGetPostQuery uses, and
the union already guards on falsy data.
… caches

Clearing the pinned post's query was the wrong move: emptying it forces a
refetch, and the delete broadcasts async, so that refetch returns the
not-yet-indexed post and puts it straight back. Greptile caught this on
the previous commit.

Records deleted author/permlink in a module-level set instead. Being
module level rather than component state is the point: the previous
version was lost when the screen unmounted, so leaving a profile and
returning brought the post back. It now suppresses the row whatever any
cache still holds, without forcing a fetch that would lose the race.

Being shared across feeds is deliberate too: deleting from a profile tab
hides the post on the main feed as well.

The feed cache patch stays, since it provides data rather than emptying
it and so does not trigger a fetch.
Comment thread src/providers/queries/postQueries/feedQueries.ts
Comment thread src/providers/queries/postQueries/feedQueries.ts
parsePost swaps author and permlink to the original entry, so the sheet's
content is the ORIGINAL rather than the wrapper the user is looking at.
On a self cross-post the author check therefore passes and deleting
destroys the original post while leaving the wrapper behind, which is not
what delete on that card means.

This PR made it worse rather than causing it: hiding the row and toasting
success told the user the cross-post was gone when their original had
been deleted instead.

Withholds delete on cross-posts, matching mute and pin, which are already
withheld through _canModerateCommunityPost for exactly this ambiguity.
The original is still deletable from its own card or screen.
@feruzm
feruzm merged commit 84971b0 into development Aug 4, 2026
15 checks passed
@feruzm
feruzm deleted the fix/feed-post-delete branch August 4, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(feed): deleting a post from a list pops the feed and leaves it stale

1 participant