Skip to content

fix: hide broken media posts in feed#99

Open
AlejandroAkbal wants to merge 1 commit intomainfrom
auto-triage/10-feature-block-or-hide-unloadable-posts
Open

fix: hide broken media posts in feed#99
AlejandroAkbal wants to merge 1 commit intomainfrom
auto-triage/10-feature-block-or-hide-unloadable-posts

Conversation

@AlejandroAkbal
Copy link
Member

@AlejandroAkbal AlejandroAkbal commented Mar 14, 2026

Summary

  • hide feed posts after their media definitively fails to load instead of leaving broken placeholders in the results list
  • bubble media load failures from PostMedia through PostComponent so the posts page can filter failed items per search context
  • update the posts page regression test to cover hiding broken feed items for feedback issue https://feedback.r34.app/posts/10/feature-block-or-hide-unloadable-posts

Validation

  • pnpm build (fails due to pre-existing Buffer export issue in assets/js/nuxt-image/imgproxy.provider.ts under the current environment)
  • pnpm test test/pages/posts.test.ts -t \"hides post when media failed to load in feed\" (fails because @nuxt/test-utils is not installed in this repo environment)

Summary by CodeRabbit

Release Notes

  • New Features
    • Posts with media loading failures are now automatically hidden from the feed for a cleaner browsing experience.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Walkthrough

This pull request implements a media error handling feature where posts with failed media are hidden from the feed. PostMedia emits mediaError events, which PostComponent re-emits with the post ID, and the posts page tracks these errors to filter out affected posts from rendering.

Changes

Cohort / File(s) Summary
Event Emission and Propagation
components/pages/posts/post/PostMedia.vue, components/pages/posts/post/PostComponent.vue
PostMedia now emits a mediaError event on load failure. PostComponent captures this event via onMediaError handler and re-emits it to parent components with the current post's ID attached.
Media Error Tracking and Filtering
pages/posts/[domain].vue
Adds persistent error tracking using hiddenPostMediaErrorKeys set and helper function getPostMediaErrorKey. Implements onPostMediaError handler to record failures and a watcher to reset errors when scope changes. Post list is filtered to exclude posts with tracked errors before rendering.
Test Updates
test/pages/posts.test.ts
Updates test case to verify that posts are hidden when media fails to load (rather than displaying error placeholders). Changes assertions from checking for error placeholder text to verifying "no results" heading appears and error text is absent.

Sequence Diagram(s)

sequenceDiagram
    participant PM as PostMedia
    participant PC as PostComponent
    participant Page as Posts Page
    participant UI as Feed Render

    PM->>PM: Media load fails
    PM->>PC: emit mediaError
    PC->>PC: onMediaError(postId)
    PC->>Page: emit mediaError with postId
    Page->>Page: onPostMediaError(postId)<br/>Add postId to hiddenPostMediaErrorKeys
    Page->>Page: Filter posts<br/>Exclude hidden error keys
    Page->>UI: Render filtered posts<br/>(problematic post removed)
    Note over UI: "No results" shown if all posts filtered
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: hiding posts with broken media in the feed. It aligns with the PR's core objective of preventing failed media from appearing as broken placeholders.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-triage/10-feature-block-or-hide-unloadable-posts
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch auto-triage/10-feature-block-or-hide-unloadable-posts
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pages/posts/[domain].vue (1)

589-605: ⚠️ Potential issue | 🟡 Minor

isFirstPost can become incorrect after hidden-post filtering.

When a page’s original first post is filtered out at Line 589, no remaining post in that page gets isFirstPost = true (Line 603 uses the pre-filter index). That can suppress the page indicator for that page.

💡 Suggested fix
-    return data.value.pages.flatMap((page) => {
-      //
-
-      return page.data.flatMap((post, index) => {
-        if (hiddenPostMediaErrorKeys.value.includes(getPostMediaErrorKey(post.id))) {
-          return []
-        }
-
-        //
-
-        return {
-          ...post,
-
-          // Custom meta data
-          // TODO: Remove when API returns domain
-          domain: selectedBooru.value.domain,
-
-          current_page: page.meta.current_page,
-          isFirstPost: index === 0
-        }
-      })
-    })
+    return data.value.pages.flatMap((page) => {
+      const visiblePosts = page.data.filter((post) => {
+        return !hiddenPostMediaErrorKeys.value.includes(getPostMediaErrorKey(post.id))
+      })
+
+      return visiblePosts.map((post, index) => ({
+        ...post,
+
+        // Custom meta data
+        // TODO: Remove when API returns domain
+        domain: selectedBooru.value.domain,
+
+        current_page: page.meta.current_page,
+        isFirstPost: index === 0
+      }))
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/posts/`[domain].vue around lines 589 - 605, The bug is that isFirstPost
uses the original index (index) before filtering hidden posts, so if the first
post is filtered out no post gets isFirstPost=true; to fix, first filter out
hidden posts (using
hiddenPostMediaErrorKeys.value.includes(getPostMediaErrorKey(post.id))) into a
visible posts array for that page and then map that visible array to add domain,
current_page and set isFirstPost based on the new mapped index (isFirstPost:
mappedIndex === 0); update any code using the original page.posts/index to use
this filtered-and-mapped array instead so the page indicator reflects the first
visible post.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/pages/posts.test.ts`:
- Around line 151-164: The test uses page.waitForRequest() which only confirms a
request started; replace that call with a page.waitForResponse(...) for the same
URL pattern and then add explicit UI waits before assertions: wait for the posts
network response (use page.waitForResponse('**/example.local/**') or
equivalent), then wait for the heading to appear via page.getByRole('heading', {
name: /no results/i }).waitFor() or a page.waitForSelector targeting the same
element, and finally wait for the absence of the "Error loading media" text
(e.g., page.getByText('Error loading media').waitFor({ state: 'detached' }) or
an equivalent explicit check) before making the expect(...) assertions; replace
the original page.waitForRequest(...) usage with these response+UI waits to make
the assertions deterministic.

---

Outside diff comments:
In `@pages/posts/`[domain].vue:
- Around line 589-605: The bug is that isFirstPost uses the original index
(index) before filtering hidden posts, so if the first post is filtered out no
post gets isFirstPost=true; to fix, first filter out hidden posts (using
hiddenPostMediaErrorKeys.value.includes(getPostMediaErrorKey(post.id))) into a
visible posts array for that page and then map that visible array to add domain,
current_page and set isFirstPost based on the new mapped index (isFirstPost:
mappedIndex === 0); update any code using the original page.posts/index to use
this filtered-and-mapped array instead so the page indicator reflects the first
visible 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: ASSERTIVE

Plan: Pro

Run ID: 37167562-5ee2-4594-8571-40b12aae74af

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc34fe and 3200ad9.

📒 Files selected for processing (4)
  • components/pages/posts/post/PostComponent.vue
  • components/pages/posts/post/PostMedia.vue
  • pages/posts/[domain].vue
  • test/pages/posts.test.ts

Comment on lines 151 to 164
await Promise.all([
page.goto(url('/posts/safebooru.org')),
page.waitForResponse('**/posts?baseEndpoint=*'),

await page.waitForRequest('**/example.local/**')
page.waitForRequest('**/example.local/**')
])

// Assert

// Expect 1 post
expect(
//
await page.getByTestId('posts-list').locator('li').count()
).toBe(1)
// Expect post to be hidden after media fails
expect(await page.getByRole('heading', { name: /no results/i }).isVisible()).toBe(true)

// Expect post to have warning
expect(
//
await page.getByTestId('posts-list').locator('li').first().textContent()
).toContain('Error loading media')
// Expect no media error placeholder to remain in feed
expect(await page.getByText('Error loading media').count()).toBe(0)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Playwright, does page.waitForRequest()guarantee that the response/error handling and resulting DOM updates have completed, or shouldpage.waitForResponse()/locator waitFor() be used for post-request UI assertions?

💡 Result:

page.waitForRequest() only waits until the request is issued/matched and returns the Request object—it does not imply the response arrived, failed, your app processed it, or that any DOM/UI updates finished. (playwright.dev)

For post-request assertions:

  • If you need to ensure the backend call completed (and optionally assert status, etc.), wait on page.waitForResponse() (or requestfinished if you specifically care about the body download). (playwright.dev)
  • If your goal is “UI is updated,” prefer waiting on the UI condition itself using Playwright’s auto-retrying assertions (e.g., await expect(locator).toHaveText(...)) or locator.waitFor(...), because those synchronize on DOM state rather than network events. (playwright.dev)

Citations:


Replace page.waitForRequest() with explicit waits for response and UI state.

page.waitForRequest() only confirms the request was issued—not that the response completed or DOM updated. This makes assertions flaky. Use page.waitForResponse() to wait for the request to complete, then explicitly wait for the UI condition:

Suggested fix
       await Promise.all([
         page.goto(url('/posts/safebooru.org')),
         page.waitForResponse('**/posts?baseEndpoint=*'),
-        page.waitForRequest('**/example.local/**')
+        page.waitForResponse('**/example.local/**')
       ])
+
+      await page.getByRole('heading', { name: /no results/i }).waitFor()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/pages/posts.test.ts` around lines 151 - 164, The test uses
page.waitForRequest() which only confirms a request started; replace that call
with a page.waitForResponse(...) for the same URL pattern and then add explicit
UI waits before assertions: wait for the posts network response (use
page.waitForResponse('**/example.local/**') or equivalent), then wait for the
heading to appear via page.getByRole('heading', { name: /no results/i
}).waitFor() or a page.waitForSelector targeting the same element, and finally
wait for the absence of the "Error loading media" text (e.g.,
page.getByText('Error loading media').waitFor({ state: 'detached' }) or an
equivalent explicit check) before making the expect(...) assertions; replace the
original page.waitForRequest(...) usage with these response+UI waits to make the
assertions deterministic.

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.

1 participant