Skip to content

Refine attachment preview and metadata actions#2334

Merged
prxt6529 merged 4 commits intomainfrom
attachment-preview-metadata-actions
Apr 30, 2026
Merged

Refine attachment preview and metadata actions#2334
prxt6529 merged 4 commits intomainfrom
attachment-preview-metadata-actions

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Apr 30, 2026

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Prevented unnecessary notification synchronization API requests when the browser tab is hidden, reducing server load and improving efficiency.
  • New Features

    • Added IPFS metadata display for attachments with JSON syntax highlighting for improved readability.
    • Redesigned attachment actions with a dropdown menu for a cleaner interface.

Signed-off-by: prxt6529 <prxt@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@prxt6529 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 37 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a8a8c8c-9ae4-4b7d-8127-d2114021563a

📥 Commits

Reviewing files that changed from the base of the PR and between 29f6107 and 5111560.

📒 Files selected for processing (7)
  • __tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx
  • __tests__/components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.test.tsx
  • __tests__/useWaveRealtimeUpdater.test.ts
  • components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx
  • components/drops/view/item/content/attachments/JsonPreview.tsx
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts
  • contexts/wave/hooks/useWaveRealtimeUpdater.ts
📝 Walkthrough

Walkthrough

This PR adds document visibility checks across multiple components and hooks to prevent read-marking operations (wave/drop reads and notification invalidations) from executing when the browser tab is hidden. Additionally, the attachment display component now fetches and renders metadata from IPFS gateway URLs with abort handling, and reorganizes UI controls into a dropdown menu.

Changes

Cohort / File(s) Summary
Visibility-Based Read Prevention
components/brain/my-stream/MyStreamWaveChat.tsx, components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts, contexts/wave/hooks/useWaveRealtimeUpdater.ts
Add document.visibilityState checks to guard async read operations, ensuring removeWaveDeliveredNotifications() and notifications/wave/{waveId}/read API calls are skipped when the tab is hidden.
Attachment Metadata & UI Redesign
components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx
Adds metadata URL derivation from attachment URLs, fetches metadata from IPFS gateway metadata.json with abort/timeout handling, renders JSON with syntax highlighting in an animated panel, replaces inline per-action controls with a "More" dropdown menu, and adjusts CSV preview styling.
Visibility-Check Test Coverage
__tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx, __tests__/components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.test.tsx, __tests__/useWaveRealtimeUpdater.test.ts
Introduces test utilities to stub document.visibilityState, adds per-test visibility state resets, and verifies that read operations and API calls do not execute when the tab is hidden.
Attachment Display Test Updates
__tests__/components/drops/view/item/content/attachments/DropAttachmentDisplay.test.tsx
Routes attachment actions through "Attachment options" menu, adds IPFS metadata fetch coverage (including abort-signal expectations, metadata-not-found errors), updates assertions for new UI labels and metadata rendering states.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • simo6529

Poem

🐰 A rabbit hops through tabs both visible and hidden,
Metadata fetches bloom from IPFS's garden,
Read-marks pause when darkness falls,
Dropdowns now handle the calls—
More options, less clutter, the wave flows forgiven! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Refine attachment preview and metadata actions' directly relates to the main changes, specifically the DropAttachmentDisplay component refactoring that reorganizes attachment preview UI, metadata handling, and action controls.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch attachment-preview-metadata-actions

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
Review rate limit: 0/1 reviews remaining, refill in 12 minutes and 37 seconds.

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

Copy link
Copy Markdown

@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: 5

Caution

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

⚠️ Outside diff range comments (1)
components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts (1)

25-46: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hidden-tab skip currently has no retry path on visibility restore

At Line 26, hidden tabs return early, but syncReadState() is only called once. If the hook mounts while hidden, it may never mark read after becoming visible unless deps change.

Suggested fix
   useEffect(() => {
     if (!enabled) {
       return;
     }

     const syncReadState = async () => {
       if (document.visibilityState !== "visible") {
         return;
       }
@@
         .catch((error) => console.error("Failed to mark feed as read:", error));
     };

-    syncReadState();
+    void syncReadState();
+    const onVisibilityChange = () => {
+      if (document.visibilityState === "visible") {
+        void syncReadState();
+      }
+    };
+    document.addEventListener("visibilitychange", onVisibilityChange);
+    return () => {
+      document.removeEventListener("visibilitychange", onVisibilityChange);
+    };
   }, [
     enabled,
     waveId,
     removeWaveDeliveredNotifications,
     invalidateNotifications,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts`
around lines 25 - 46, The effect’s syncReadState in useWaveDropsNotificationRead
returns early when document.visibilityState !== "visible", so if the hook mounts
while hidden it never retries when the tab becomes visible; modify the effect to
register a "visibilitychange" listener that calls syncReadState when
document.visibilityState === "visible" (and still call syncReadState immediately
on mount), ensure the listener is cleaned up in the effect return, and keep the
existing logic that calls removeWaveDeliveredNotifications(waveId) and then
commonApiPostWithoutBodyAndResponse(...) followed by invalidateNotifications()
with error handling intact.
🧹 Nitpick comments (3)
components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx (3)

643-644: ⚖️ Poor tradeoff

Complex regex for JSON tokenization.

The regex complexity (35) exceeds the SonarCloud threshold (20). While this pattern correctly handles JSON syntax highlighting needs, consider extracting it into a separate utility or using a dedicated tokenizer library if maintenance becomes difficult.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx`
around lines 643 - 644, The JSON_TOKEN_PATTERN regex in
DropAttachmentDisplay.tsx is flagged for complexity; extract this constant into
a dedicated utility module (e.g., jsonTokenizeUtils or jsonHighlighter) and
import it into DropAttachmentDisplay to reduce complexity in the component, or
replace usage with a stable JSON tokenizer/highlighter library; update
references to JSON_TOKEN_PATTERN in DropAttachmentDisplay (and any helper
functions that use it) to import from the new module and add a small unit test
in the util to cover tokenization behavior.

308-327: ⚖️ Poor tradeoff

Consider adding a size limit for metadata responses.

Unlike fetchCsvPreviewText, this function doesn't enforce any size limit. A malicious or unexpectedly large metadata.json could consume excessive memory. Consider adding a similar size check or using streaming with a cap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx`
around lines 308 - 327, fetchMetadataPreviewText currently reads the entire
response into memory with no size checks; add a cap similar to
fetchCsvPreviewText to prevent huge or malicious payloads by enforcing a
MAX_METADATA_BYTES (e.g. 1_000_000) and refusing responses larger than that:
first check response.headers.get("content-length") and throw if it exceeds the
limit, then if unknown read response.body as a stream and accumulate bytes up to
the same limit, aborting/throwing when exceeded; only after confirming the
payload is within the limit convert to text and attempt JSON.parse so
fetchMetadataPreviewText enforces a hard size cap while preserving JSON
detection.

69-69: 💤 Low value

Prefer indexOf over findIndex for simple string search.

When searching for a specific value rather than matching a predicate, indexOf is more idiomatic and slightly more efficient.

Suggested change
-    const ipfsIndex = pathSegments.findIndex((segment) => segment === "ipfs");
+    const ipfsIndex = pathSegments.indexOf("ipfs");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx` at
line 69, The code uses pathSegments.findIndex((segment) => segment === "ipfs")
to locate the "ipfs" segment; replace this with the simpler, more idiomatic
pathSegments.indexOf("ipfs") in DropAttachmentDisplay (variable ipfsIndex) so
the intent is clearer and slightly more efficient—update any subsequent logic
that reads ipfsIndex (e.g., checks for -1) to remain correct after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx`:
- Around line 254-272: The test "does not call the read endpoint on unmount when
the tab is hidden" is missing an assertion that delivered-notification cleanup
is skipped; update that test (the one rendering MyStreamWaveChat and calling
unmount()) to also assert mockRemoveWaveDeliveredNotifications was not called by
adding expect(mockRemoveWaveDeliveredNotifications).not.toHaveBeenCalled()
alongside the existing assertions for commonApiPostWithoutBodyAndResponse and
invalidateNotificationsMock.

In
`@__tests__/components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.test.tsx`:
- Around line 93-109: The test for the hidden-tab case is missing an assertion
that delivered notifications aren't removed; update the "does not call the read
endpoint when the tab is hidden" test to also assert
removeWaveDeliveredNotifications was not called by adding
expect(removeWaveDeliveredNotifications).not.toHaveBeenCalled(), locating this
near the existing expects for commonApiPostWithoutBodyAndResponse and
invalidateNotifications in the test that renders TestComponent (which exercises
useWaveDropsNotificationRead).

In `@__tests__/useWaveRealtimeUpdater.test.ts`:
- Around line 239-258: The test for hidden-wave behavior should also assert that
the delivered-notification side effect doesn't run: after invoking
useWaveRealtimeUpdater's processIncomingDrop (see processIncomingDrop and
ProcessIncomingDropType) and flushing promises, add an assertion that
props.removeWaveDeliveredNotifications was not called (in addition to the
existing expect on commonApiPostWithoutBodyAndResponse) to fully verify
hidden-tab behavior.

In `@components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx`:
- Around line 1151-1157: In handleMenuCopyLink, remove the nested
globalThis.window.setTimeout(() => setCopiedLink(false), 0) so the copiedLink
state isn’t immediately cleared; instead call await handleCopyLink(), then close
the menu with setIsMoreMenuOpen(false) and rely on the existing auto-reset
effect for copiedLink (lines around handleMenuCopyLink, handleCopyLink,
setIsMoreMenuOpen, setCopiedLink) to clear the “Copied” feedback after its
1500ms timeout.

In `@contexts/wave/hooks/useWaveRealtimeUpdater.ts`:
- Around line 190-193: The code only guards markWaveAsRead with
document.visibilityState but still removes "delivered-notification" entries
while the tab is hidden, which can clear local unread state without a
server-side read; inside useWaveRealtimeUpdater, add the same visibility check
before performing the delivered-notification removal (or move the visibility
guard to encompass both actions) so that delivered notifications are not removed
when document.visibilityState !== "visible" — update the logic around the
delivered-notification removal code to consult document.visibilityState (reuse
the markWaveAsRead guard or wrap the removal block) to ensure local unread state
isn't cleared while hidden.

---

Outside diff comments:
In `@components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts`:
- Around line 25-46: The effect’s syncReadState in useWaveDropsNotificationRead
returns early when document.visibilityState !== "visible", so if the hook mounts
while hidden it never retries when the tab becomes visible; modify the effect to
register a "visibilitychange" listener that calls syncReadState when
document.visibilityState === "visible" (and still call syncReadState immediately
on mount), ensure the listener is cleaned up in the effect return, and keep the
existing logic that calls removeWaveDeliveredNotifications(waveId) and then
commonApiPostWithoutBodyAndResponse(...) followed by invalidateNotifications()
with error handling intact.

---

Nitpick comments:
In `@components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx`:
- Around line 643-644: The JSON_TOKEN_PATTERN regex in DropAttachmentDisplay.tsx
is flagged for complexity; extract this constant into a dedicated utility module
(e.g., jsonTokenizeUtils or jsonHighlighter) and import it into
DropAttachmentDisplay to reduce complexity in the component, or replace usage
with a stable JSON tokenizer/highlighter library; update references to
JSON_TOKEN_PATTERN in DropAttachmentDisplay (and any helper functions that use
it) to import from the new module and add a small unit test in the util to cover
tokenization behavior.
- Around line 308-327: fetchMetadataPreviewText currently reads the entire
response into memory with no size checks; add a cap similar to
fetchCsvPreviewText to prevent huge or malicious payloads by enforcing a
MAX_METADATA_BYTES (e.g. 1_000_000) and refusing responses larger than that:
first check response.headers.get("content-length") and throw if it exceeds the
limit, then if unknown read response.body as a stream and accumulate bytes up to
the same limit, aborting/throwing when exceeded; only after confirming the
payload is within the limit convert to text and attempt JSON.parse so
fetchMetadataPreviewText enforces a hard size cap while preserving JSON
detection.
- Line 69: The code uses pathSegments.findIndex((segment) => segment === "ipfs")
to locate the "ipfs" segment; replace this with the simpler, more idiomatic
pathSegments.indexOf("ipfs") in DropAttachmentDisplay (variable ipfsIndex) so
the intent is clearer and slightly more efficient—update any subsequent logic
that reads ipfsIndex (e.g., checks for -1) to remain correct after the change.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 085b4fce-c91f-40ba-9994-0e6060859ffe

📥 Commits

Reviewing files that changed from the base of the PR and between 16fa642 and 29f6107.

📒 Files selected for processing (8)
  • __tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx
  • __tests__/components/drops/view/item/content/attachments/DropAttachmentDisplay.test.tsx
  • __tests__/components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.test.tsx
  • __tests__/useWaveRealtimeUpdater.test.ts
  • components/brain/my-stream/MyStreamWaveChat.tsx
  • components/drops/view/item/content/attachments/DropAttachmentDisplay.tsx
  • components/waves/drops/wave-drops-all/hooks/useWaveDropsNotificationRead.ts
  • contexts/wave/hooks/useWaveRealtimeUpdater.ts

Comment thread __tests__/components/brain/my-stream/MyStreamWaveChat.test.tsx
Comment thread __tests__/useWaveRealtimeUpdater.test.ts
Comment thread contexts/wave/hooks/useWaveRealtimeUpdater.ts
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@prxt6529 prxt6529 merged commit fe6bcd5 into main Apr 30, 2026
8 checks passed
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.

2 participants