Skip to content

Align notification avatars with IPFS gateway fallback#2229

Merged
prxt6529 merged 7 commits intomainfrom
notifications-ipfs-avatar-fallback
Apr 8, 2026
Merged

Align notification avatars with IPFS gateway fallback#2229
prxt6529 merged 7 commits intomainfrom
notifications-ipfs-avatar-fallback

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Apr 8, 2026

Summary by CodeRabbit

  • New Features

    • Improved avatar and notification image handling with smarter retries, unoptimized fallbacks, and placeholder suppression when images are unavailable.
    • Better IPFS gateway resolution that preserves ports/paths and falls back to alternative gateways as needed.
  • Tests

    • Added extensive tests covering image load retries, gateway fallback progression, and URL resolution behavior.
  • Chores

    • Adjusted typecheck configuration and script to tighten build-time type checking.

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

coderabbitai Bot commented Apr 8, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 8 seconds.

⌛ 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: 102faa67-4af4-48a6-9a72-b84c5a7a5c0b

📥 Commits

Reviewing files that changed from the base of the PR and between ce80f3c and 9ab7242.

📒 Files selected for processing (12)
  • __tests__/components/brain/notifications/subcomponents/NotificationHeader.test.tsx
  • __tests__/components/common/OverlappingAvatars.test.tsx
  • __tests__/components/ipfs/IPFSContext.test.tsx
  • __tests__/helpers/image.helpers.test.ts
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/brain/notifications/subcomponents/NotificationHeader.tsx
  • components/common/OverlappingAvatars.tsx
  • components/waves/drop/SingleWaveDropLog.tsx
  • components/waves/drops/WaveDropRatings.tsx
  • components/waves/leaderboard/drops/header/WaveleaderboardDropRaters.tsx
  • knip.jsonc
  • package.json
📝 Walkthrough

Walkthrough

Adds a client hook to manage image load retries across Arweave/IPFS gateways, integrates it into avatar and notification header components, expands IPFS URL rewriting to handle ipfs.io gateway URLs, updates image-scaling helpers, and adds tests and typecheck config changes.

Changes

Cohort / File(s) Summary
New Hook
components/common/image/useGatewayImageLoadState.ts
Adds useGatewayImageLoadState(src) to manage candidate gateway URLs, optimized/unoptimized modes, error handling, and expose { activeSrc, isPlaceholder, unoptimized, handleError }.
Component Integrations
components/common/OverlappingAvatars.tsx, components/brain/notifications/subcomponents/NotificationHeader.tsx
Replace local img-error state with useGatewayImageLoadState; conditionally render Image based on isPlaceholder/activeSrc, forward unoptimized and onError, and derive src via getScaledResolvedImageUri(activeSrc, ...).
Avatar PFP Handling
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
Stop parsing/normalizing profile.pfp; use profile.pfp ?? null directly when building avatar items.
IPFS URL Rewriting
components/ipfs/IPFSContext.tsx
Extend resolveIpfsUrlSync to also rewrite https://ipfs.io/... (and www.ipfs.io) /ipfs/... URLs to configured gateway host/port/basePath using a new joinUrlPaths helper; preserves path/query/hash when not rewriting.
Image helpers
helpers/image.helpers.ts, __tests__/helpers/image.helpers.test.ts
Introduce getScaledResolvedImageUri(resolvedUrl, scale); getScaledImageUri now resolves via resolveIpfsUrlSync then delegates to new helper; add test ensuring already-resolved https://ipfs.io/... is returned unchanged by resolved-scaling helper.
Tests
__tests__/components/brain/notifications/subcomponents/NotificationHeader.test.tsx, __tests__/components/common/OverlappingAvatars.test.tsx, __tests__/components/ipfs/IPFSContext.test.tsx
Add tests covering gateway-fallback image retry progression, unoptimized flag toggling, placeholder fallback behavior, and IPFS gateway URL rewriting with custom ports/base paths.
Typecheck / Config
next-env.typecheck.d.ts, tsconfig.typecheck.json, package.json
Add next-env.typecheck.d.ts; narrow typecheck tsconfig include/exclude; update typecheck npm script to run tsc -p tsconfig.typecheck.json.

Sequence Diagram(s)

sequenceDiagram
    participant Component
    participant Hook as useGatewayImageLoadState
    participant Image as Image Element
    participant GatewayList as Fallback Gateways

    Component->>Hook: useGatewayImageLoadState(src)
    Hook->>GatewayList: getArweaveGatewayFallbackUrls(src)
    GatewayList-->>Hook: [gateway1, gateway2, ...]
    Hook->>Hook: Initialize: mode="optimized", candidateIndex=0
    Hook-->>Component: activeSrc=gateway1, unoptimized=false

    Component->>Image: Render <Image src={activeSrc} unoptimized={false} />
    Image->>Image: Load attempt from gateway1
    Image--XImage: Load fails
    Image->>Component: onError triggered

    Component->>Hook: handleError()
    Hook->>Hook: Switch to mode="unoptimized"
    Hook-->>Component: activeSrc=gateway1, unoptimized=true

    Component->>Image: Re-render <Image src={gateway1} unoptimized={true} />
    Image->>Image: Load attempt (unoptimized)
    Image--XImage: Load fails again
    Image->>Component: onError triggered

    Component->>Hook: handleError()
    Hook->>Hook: Increment candidateIndex, mode="optimized"
    Hook-->>Component: activeSrc=gateway2, unoptimized=false

    Component->>Image: Re-render <Image src={gateway2} unoptimized={false} />
    Image->>Image: Load attempt from gateway2
    Image-->>Image: Success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ragnep
  • simo6529

Poem

🐰 a rabbit's cheer
Gateways tumble, one by one,
I nibble hops until it's done.
Images leap, then find their way—
IPFS paths saved for play! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: introducing IPFS gateway fallback handling for notification avatars across multiple components.

✏️ 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 notifications-ipfs-avatar-fallback

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.

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

prxt6529 commented Apr 8, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/brain/notifications/subcomponents/NotificationHeader.tsx`:
- Around line 25-33: activeSrc returned from useGatewayImageLoadState() is
already the resolved concrete URL; wrapping it in getScaledImageUri() causes
resolveIpfsUrlSync() to run again and undo gateway fallback behavior. Fix: when
rendering the Image in NotificationHeader (and likewise in OverlappingAvatars),
detect if activeSrc is already resolved and avoid calling getScaledImageUri on
it — instead apply scaling directly to the resolved URL or use a helper that
only appends sizing params without re-running resolveIpfsUrlSync; update the
Image src usage to pass the already-resolved activeSrc (or a non-resolving
scaling helper) so gateway fallback attempts are preserved.

In `@components/ipfs/IPFSContext.tsx`:
- Around line 90-106: The rewrite currently only replaces
parsedUrl.hostname/host using getConfiguredIpfsGatewayHost(), which loses any
configured port or base path; instead, parse the full configured gateway
endpoint and reconstruct the rewritten URL by adopting its protocol, hostname,
port and base pathname. In practice: call new URL(configuredGatewayEndpoint)
(from the value returned by getConfiguredIpfsGatewayHost() or adjust to fetch
the full endpoint), then set parsedUrl.protocol, parsedUrl.hostname,
parsedUrl.port and set parsedUrl.pathname = joinPaths(configuredBase.pathname,
parsedUrl.pathname) (ensuring single slashes) and update host accordingly so
functions/variables in this block (parsedUrl, configuredHost /
configuredGatewayEndpoint, parsedUrl.pathname) preserve port and non-root base
paths when rewriting ipfs.io URLs.
🪄 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: 52af748d-6924-41eb-8724-bd48c902f8f7

📥 Commits

Reviewing files that changed from the base of the PR and between c11f6fc and 8efff79.

📒 Files selected for processing (8)
  • __tests__/components/brain/notifications/subcomponents/NotificationHeader.test.tsx
  • __tests__/components/common/OverlappingAvatars.test.tsx
  • __tests__/components/ipfs/IPFSContext.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/brain/notifications/subcomponents/NotificationHeader.tsx
  • components/common/OverlappingAvatars.tsx
  • components/common/image/useGatewayImageLoadState.ts
  • components/ipfs/IPFSContext.tsx

Comment thread components/brain/notifications/subcomponents/NotificationHeader.tsx
Comment thread components/ipfs/IPFSContext.tsx Outdated
prxt6529 added 4 commits April 8, 2026 10:40
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
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.

🧹 Nitpick comments (2)
__tests__/helpers/image.helpers.test.ts (1)

40-44: Test correctly verifies no re-resolution, but consider moving to separate describe block.

The test validates the expected behavior of getScaledResolvedImageUri. However, it's placed inside the describe("getScaledImageUri") block while testing a different function.

♻️ Suggested organization
 });
+
+describe("getScaledResolvedImageUri", () => {
+  it("does not re-resolve already concrete urls", () => {
+    const url = "https://ipfs.io/ipfs/QmConcrete";
+    expect(getScaledResolvedImageUri(url, ImageScale.W_AUTO_H_50)).toBe(url);
+  });
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/helpers/image.helpers.test.ts` around lines 40 - 44, The test
asserting that a concrete IPFS URL is not re-resolved belongs to the
getScaledResolvedImageUri behavior, not getScaledImageUri; move the it("does not
re-resolve already concrete urls", ...) out of the describe("getScaledImageUri")
block and into a new or existing describe("getScaledResolvedImageUri") block so
tests are organized by the function under test (referencing function name
getScaledResolvedImageUri and existing describe for getScaledImageUri to locate
where to relocate it).
helpers/image.helpers.ts (1)

53-55: Existing call sites now double-resolve URLs due to new internal call in getScaledImageUri.

The refactored getScaledImageUri function (line 54) now calls resolveIpfsUrlSync internally, creating redundant resolution at three existing call sites:

  • WaveleaderboardDropRaters.tsx:67–71: resolveIpfsUrlSync(getScaledImageUri(...))
  • WaveDropRatings.tsx:86–90: resolveIpfsUrlSync(getScaledImageUri(...))
  • SingleWaveDropLog.tsx:104: getScaledImageUri(resolveIpfsUrlSync(...), ...)

While functionally safe (resolution is idempotent for both IPFS and non-IPFS URLs), these patterns waste cycles and introduce inconsistency. Clean up by removing the outer resolveIpfsUrlSync calls in the first two files and the inner resolveIpfsUrlSync call in the third.

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

In `@helpers/image.helpers.ts` around lines 53 - 55, getScaledImageUri now calls
resolveIpfsUrlSync internally, causing double-resolution at the three call
sites; remove the redundant resolveIpfsUrlSync calls so each URL is resolved
only once. Specifically: in WaveleaderboardDropRaters and WaveDropRatings remove
the outer resolveIpfsUrlSync(...) wrapping getScaledImageUri(...), and in
SingleWaveDropLog change the call to pass the raw URL into
getScaledImageUri(...) (remove the inner resolveIpfsUrlSync there). Keep using
getScaledImageUri(url, scale) and delete the extra resolveIpfsUrlSync
invocations to avoid wasted work and inconsistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@__tests__/helpers/image.helpers.test.ts`:
- Around line 40-44: The test asserting that a concrete IPFS URL is not
re-resolved belongs to the getScaledResolvedImageUri behavior, not
getScaledImageUri; move the it("does not re-resolve already concrete urls", ...)
out of the describe("getScaledImageUri") block and into a new or existing
describe("getScaledResolvedImageUri") block so tests are organized by the
function under test (referencing function name getScaledResolvedImageUri and
existing describe for getScaledImageUri to locate where to relocate it).

In `@helpers/image.helpers.ts`:
- Around line 53-55: getScaledImageUri now calls resolveIpfsUrlSync internally,
causing double-resolution at the three call sites; remove the redundant
resolveIpfsUrlSync calls so each URL is resolved only once. Specifically: in
WaveleaderboardDropRaters and WaveDropRatings remove the outer
resolveIpfsUrlSync(...) wrapping getScaledImageUri(...), and in
SingleWaveDropLog change the call to pass the raw URL into
getScaledImageUri(...) (remove the inner resolveIpfsUrlSync there). Keep using
getScaledImageUri(url, scale) and delete the extra resolveIpfsUrlSync
invocations to avoid wasted work and inconsistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d706aba-0d30-4d75-b913-1759fc1f3b21

📥 Commits

Reviewing files that changed from the base of the PR and between 8efff79 and ce80f3c.

📒 Files selected for processing (9)
  • __tests__/components/ipfs/IPFSContext.test.tsx
  • __tests__/helpers/image.helpers.test.ts
  • components/brain/notifications/subcomponents/NotificationHeader.tsx
  • components/common/OverlappingAvatars.tsx
  • components/ipfs/IPFSContext.tsx
  • helpers/image.helpers.ts
  • next-env.typecheck.d.ts
  • package.json
  • tsconfig.typecheck.json
✅ Files skipped from review due to trivial changes (2)
  • next-env.typecheck.d.ts
  • tests/components/ipfs/IPFSContext.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/ipfs/IPFSContext.tsx

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

sonarqubecloud Bot commented Apr 8, 2026

@prxt6529 prxt6529 merged commit 262eec4 into main Apr 8, 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