Conversation
|
Warning Rate limit exceeded
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 48 minutes and 14 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📝 WalkthroughWalkthroughAdds profile-curation management: per-tab icons/actions, DnD-enabled desktop curation tabs with optimistic reorder, profile-wave query/mutation hooks and API support, profile curation picker UI, and related menu/edit/delete flows integrated across MyStream and UserPage components. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as MyStreamWaveDesktopTabs
participant Query as ReactQuery Cache
participant API as Server
User->>UI: Drag curation tab (start)
UI->>Query: cancel wave curations query & snapshot data (onMutate)
UI->>Query: write optimistic reordered curations (update priority_order)
UI->>API: POST /waves/{waveId}/curations/{curationId} { targetPriorityOrder }
API-->>UI: 200 updated curation
UI->>Query: patch updated curation into cache & re-sort (onSuccess)
UI-->>User: update tab order UI
Note over UI,Query: onError => restore snapshot & show error toast
sequenceDiagram
participant User
participant UI as UserPageProfileWave
participant Query as ReactQuery Cache
participant API as Server
User->>UI: Open curation selector
UI->>Query: useProfileWave(identity) => GET /profiles/{identity}/wave
API-->>Query: { profile_wave_id, profile_curation_id }
Query-->>UI: display curations + current profile curation
User->>UI: Select different curation
UI->>Query: updateProfileWave(waveId, profileCurationId)
Query->>API: POST /profiles/{identity}/wave { wave_id, profile_curation_id? }
API-->>Query: success
Query->>Query: setProfileWaveQueryData(...) + invalidate related keys
UI-->>User: close picker and show success toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 (2)
openapi.yaml (1)
1591-1607:⚠️ Potential issue | 🟠 MajorAlign the DELETE reaction contract with the caller.
hooks/drops/useDropReaction.tsremoves reactions viacommonApiDelete({ endpoint })without a body, but this spec requiresApiAddReactionToDropRequestfor DELETE. Either remove the request body here, or update the caller/backend contract to send and require it consistently.🐛 Proposed OpenAPI fix if DELETE removes the authenticated user's current reaction
delete: tags: - Drops summary: Remove reaction from a drop operationId: removeReactionFromDrop parameters: - name: dropId in: path required: true schema: type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ApiAddReactionToDropRequest" responses: "201": description: successful operation🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openapi.yaml` around lines 1591 - 1607, The DELETE operation removeReactionFromDrop currently declares a required requestBody referencing ApiAddReactionToDropRequest but the client hook hooks/drops/useDropReaction.ts calls commonApiDelete({ endpoint }) with no body; reconcile by removing the requestBody from the OpenAPI DELETE (so remove the requestBody block from operationId removeReactionFromDrop and document that DELETE targets the authenticated user's reaction), or alternatively change the caller/backend contract so commonApiDelete sends the ApiAddReactionToDropRequest payload and make the backend handler accept and validate that schema; pick one consistent approach and update the OpenAPI spec, the hook (useDropReaction.ts), and any backend handler to match.hooks/useProfileWaveMutation.ts (1)
23-28:⚠️ Potential issue | 🟠 MajorSynchronize profile identity key precedence with userPageProfileWave.helpers.ts.
The mutation uses
query>handle>primary_wallet>idfor identity resolution, but the profile page helper useshandle>query>primary_wallet. When both fields exist, this causes the mutation to update/invalidate a different cache key than the one the profile page reads from.Suggested fix
const getProfileIdentityKey = (profile: ApiIdentity | null): string | null => - profile?.query ?? - profile?.handle ?? - profile?.primary_wallet ?? - profile?.id ?? - null; + profile?.handle?.trim() || + profile?.query?.trim() || + profile?.primary_wallet?.trim() || + profile?.id?.trim() || + null;Also applies to the second usage at lines 57–68.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useProfileWaveMutation.ts` around lines 23 - 28, The identity-key precedence in getProfileIdentityKey is wrong: change it to match userPageProfileWave.helpers.ts by returning profile?.handle first, then profile?.query, then profile?.primary_wallet, then profile?.id (or null); update the same precedence for the second usage in this file (the other place where the identity key is computed around lines 57–68), and ensure null is returned when none exist so cache keys/invalidation align with the profile page.
🧹 Nitpick comments (1)
components/brain/my-stream/MyStreamWaveDesktopTabs.tsx (1)
80-84: Prefersliceoverreplacefor prefix stripping.
String.prototype.replacewith a string pattern only replaces the first occurrence, sogetCurationIdFromTabKeysilently does the right thing today but is fragile if a curation id ever contains"curation:"as a substring. A prefix-slice makes the intent explicit and eliminates that class of edge case.♻️ Proposed refactor
-const getCurationIdFromTabKey = (key: string): string => - key.replace("curation:", ""); +const CURATION_TAB_KEY_PREFIX = "curation:"; + +const getCurationIdFromTabKey = (key: string): string => + key.startsWith(CURATION_TAB_KEY_PREFIX) + ? key.slice(CURATION_TAB_KEY_PREFIX.length) + : key;The same constant can then back
key.startsWith("curation:")at line 648 andgetCurationTabKeyat line 81.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/brain/my-stream/MyStreamWaveDesktopTabs.tsx` around lines 80 - 84, The helper getCurationIdFromTabKey currently uses key.replace("curation:", "") which can accidentally remove later occurrences; change it to strip the fixed prefix explicitly (use a shared constant like PREFIX = "curation:" and return key.slice(PREFIX.length) when key.startsWith(PREFIX)) and update callers (e.g., the startsWith check at the location currently using "curation:") to use that same PREFIX constant; adjust getCurationTabKey to construct keys with PREFIX as well.
🤖 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/my-stream/tabs/MyStreamWaveCurationTabMenu.tsx`:
- Around line 55-69: The onSuccess block updates wave and drop curation caches
but misses invalidating the profile-wave cache which can still reference the
deleted curation; add a call to queryClient.invalidateQueries for the
profile-wave query after the other invalidations (use the existing profile-wave
query key helper, e.g. getProfileWaveQueryKey(...) or the ["profile-wave", ...]
key shape your codebase uses) so the profile-wave cache is refreshed and any
profile_curation_id pointing to the deleted curation is cleared.
In `@components/common/TabToggle.tsx`:
- Around line 65-102: The tablist currently contains non-tab interactive
controls via option.action inside the mapped option, which violates ARIA tablist
structure; update the TabToggle component so the element with role="tablist"
only contains the tab buttons (the <button> rendered for each option keyed by
option.key and using aria-controls/aria-selected), and render option.action
outside that container as a sibling/presentation element (for example render the
action in a separate wrapper after the tablist or inline as a sibling div next
to each tab button that is not a descendant of the role="tablist"); ensure you
keep the existing button attributes (onClick, aria-selected, aria-controls) and
move any styling for option.action into the new wrapper so interactive controls
are no longer inside the element with role="tablist".
In `@components/user/waves/UserPageProfileWave.tsx`:
- Around line 252-262: initialProfileWave is seeded with profile_curation_id:
null causing a UI flicker; change the seeding so
initialProfileWave.profile_curation_id uses the actual
resolvedProfile.profile_curation_id (not null) and include that property in the
useMemo dependency array so the memo updates when
resolvedProfile.profile_curation_id is present before useProfileWave hydrates;
update the object passed to useProfileWave accordingly so the initial value
reflects any SSR/prefetched curation id.
In `@hooks/useProfileWave.ts`:
- Around line 10-11: getProfileWaveQueryKey currently lowercases identity but
leaves trimming to callers, causing mismatched React Query keys between
useProfileWave (which trims) and useProfileWaveMutation (which uses
getProfileIdentityKey() directly); update getProfileWaveQueryKey to perform full
normalization (identity.trim().toLowerCase()) and then update useProfileWave and
useProfileWaveMutation to call the helper unchanged so both use the same
normalized key; ensure you reference and update the exported
getProfileWaveQueryKey, and adjust any call sites (useProfileWave,
useProfileWaveMutation, and places using getProfileIdentityKey()) to rely on the
helper rather than trimming themselves.
In `@hooks/waves/useWaveCurationReorderMutation.ts`:
- Around line 174-232: The code reads variables.curation.id unguarded when
computing pendingCurationId even though useMutation().variables may be
undefined; update the return to safely access variables (e.g., check isPending
&& variables && variables.curation ? variables.curation.id : null or use
optional chaining variables?.curation?.id) so pendingCurationId never
dereferences undefined; keep the rest of moveCuration, reorderCuration and
mutate usage unchanged.
---
Outside diff comments:
In `@hooks/useProfileWaveMutation.ts`:
- Around line 23-28: The identity-key precedence in getProfileIdentityKey is
wrong: change it to match userPageProfileWave.helpers.ts by returning
profile?.handle first, then profile?.query, then profile?.primary_wallet, then
profile?.id (or null); update the same precedence for the second usage in this
file (the other place where the identity key is computed around lines 57–68),
and ensure null is returned when none exist so cache keys/invalidation align
with the profile page.
In `@openapi.yaml`:
- Around line 1591-1607: The DELETE operation removeReactionFromDrop currently
declares a required requestBody referencing ApiAddReactionToDropRequest but the
client hook hooks/drops/useDropReaction.ts calls commonApiDelete({ endpoint })
with no body; reconcile by removing the requestBody from the OpenAPI DELETE (so
remove the requestBody block from operationId removeReactionFromDrop and
document that DELETE targets the authenticated user's reaction), or
alternatively change the caller/backend contract so commonApiDelete sends the
ApiAddReactionToDropRequest payload and make the backend handler accept and
validate that schema; pick one consistent approach and update the OpenAPI spec,
the hook (useDropReaction.ts), and any backend handler to match.
---
Nitpick comments:
In `@components/brain/my-stream/MyStreamWaveDesktopTabs.tsx`:
- Around line 80-84: The helper getCurationIdFromTabKey currently uses
key.replace("curation:", "") which can accidentally remove later occurrences;
change it to strip the fixed prefix explicitly (use a shared constant like
PREFIX = "curation:" and return key.slice(PREFIX.length) when
key.startsWith(PREFIX)) and update callers (e.g., the startsWith check at the
location currently using "curation:") to use that same PREFIX constant; adjust
getCurationTabKey to construct keys with PREFIX as well.
🪄 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: 6c20fd32-8350-4568-a8d9-fdaec2e02356
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
components/brain/my-stream/MyStreamWaveDesktopTabs.tsxcomponents/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsxcomponents/common/TabToggle.tsxcomponents/user/waves/UserPageProfileWave.tsxcomponents/user/waves/UserPageProfileWaveShared.tsxcomponents/user/waves/userPageProfileWave.helpers.tscomponents/waves/groups/curation/WaveActiveCurationSection.tsxhooks/useProfileWave.tshooks/useProfileWaveMutation.tshooks/waves/useWaveCurationReorderMutation.tshooks/waves/useWaveCurations.tsopenapi.yamlpackage.jsonservices/api/profile-wave-api.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/my-stream/MyStreamWaveDesktopTabs.tsx`:
- Around line 588-633: The tablist currently wraps interactive non-tab controls
causing ARIA issues; update the JSX so the element with role="tablist" only
contains the actual tab buttons (e.g., the elements rendered by
DesktopTabOption/SortableCurationTabOption that represent role="tab") and move
action controls (menu trigger) and drag handles out of that container as
siblings. Concretely, refactor the markup around the div with role="tablist" so
it wraps a child container that maps only the tab-button elements (from
DesktopTabOption/SortableCurationTabOption) and render the action/drag buttons
either in an adjacent toolbar or as siblings next to each tab entry (or render
them conditionally outside the tablist when
canDragCurations/isCurationReorderPending); ensure
getCurationIdFromTabKey/onSelectCuration behavior and SortableContext/DndContext
still apply but that the drag-handle/button and menu trigger are not descendants
of the element with role="tablist".
- Around line 148-157: The MyStreamWaveDesktopTabs component renders the prop
named leadingIcon after the label, causing a mismatch with TabToggle which
renders leadingIcon before the label; update MyStreamWaveDesktopTabs (the JSX
that outputs option.label and option.leadingIcon) to place option.leadingIcon
before the label so the prop semantics match TabToggle, or alternatively rename
the prop to trailingIcon across this component and its callers; modify either
the render order in MyStreamWaveDesktopTabs or perform a prop rename in both
MyStreamWaveDesktopTabs and any call sites to keep naming consistent.
In `@components/common/TabToggle.tsx`:
- Around line 66-92: The DOM order mismatch causes keyboard focus to follow DOM
rather than visual order; update TabToggle to render each option and its
optional action together in DOM order instead of batching tabs then actions and
using style={{ order }}. Concretely, change the JSX that currently maps options
twice (the options.map renderTabButton block and the subsequent options.map
creating action wrappers) to a single map over options that returns a per-option
wrapper containing renderTabButton(option, { order: ... }) and the option.action
(if present), remove the use of the tw-contents element with role="tablist" (or
move role="tablist" to the wrapper element that actually contains the
interactive tab buttons), and eliminate reliance on inline flex order so
keyboard focus traversal matches visual order.
🪄 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: 248ba215-d4be-40f9-858e-59d358e42885
📒 Files selected for processing (6)
components/brain/my-stream/MyStreamWaveDesktopTabs.tsxcomponents/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsxcomponents/common/TabToggle.tsxhooks/useProfileWave.tshooks/useProfileWaveMutation.tshooks/waves/useWaveCurationReorderMutation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- hooks/useProfileWaveMutation.ts
- hooks/useProfileWave.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/user/waves/UserPageProfileWaveMasonry.tsx (1)
373-381:⚠️ Potential issue | 🟠 MajorAvoid remounting Masonry on every page append/remove.
Line 381 includes
drops.lengthin themasonryKey, which means React will remount the Masonry component every time the number of drops changes—on infinite-scroll appends, curation removals, and similar operations. This discards masonic's internal measurement and layout cache, forcing expensive recalculation and potential visual jank.Since the Masonry component already receives
items={masonryItems}anditemKey={(item) => item.drop.stableKey}, masonic can reconcile added/removed drops incrementally. ThemasonryTopItemsKey(first 8 drops) provides additional identity tracking for the visible top of the grid. Consider removingdrops.lengthfrom the key unless there is a verified masonic bug requiring a full reset on length-only changes.Suggested fix
- const masonryKey = `${curationId}-${containerWidth}-${drops.length}-${masonryTopItemsKey}`; + const masonryKey = `${curationId}-${containerWidth}-${masonryTopItemsKey}`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/user/waves/UserPageProfileWaveMasonry.tsx` around lines 373 - 381, The Masonry key currently forces full remounts because it includes drops.length; update the key generation so it no longer includes drops.length (keep curationId, containerWidth and masonryTopItemsKey) so masonic can reconcile via items={masonryItems} and itemKey={(item) => item.drop.stableKey}; specifically, modify the masonryKey construction that references masonryTopItemsKey and drops.length to remove drops.length while preserving curationId and containerWidth to avoid unnecessary remounts on append/remove.
♻️ Duplicate comments (1)
components/user/waves/UserPageProfileWave.tsx (1)
252-258:⚠️ Potential issue | 🟡 MinorSeed the initial curation id when it is already available.
profile_curation_idis still initialized asnull, so the selected curation label/icon can briefly disappear untiluseProfileWaverefetches. IfresolvedProfilecarries the curation id, thread it through the initial data.Suggested fix
const initialProfileWave = useMemo<ApiProfileWaveResponse>( () => ({ profile_wave_id: resolvedProfile.profile_wave_id, - profile_curation_id: null, + profile_curation_id: resolvedProfile.profile_curation_id ?? null, }), - [resolvedProfile.profile_wave_id] + [resolvedProfile.profile_wave_id, resolvedProfile.profile_curation_id] );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/user/waves/UserPageProfileWave.tsx` around lines 252 - 258, The initialProfileWave useMemo currently sets profile_curation_id to null causing UI flicker; change the initializer in the initialProfileWave (the useMemo that returns ApiProfileWaveResponse) to seed profile_curation_id from resolvedProfile.profile_curation_id when available (fall back to null only if undefined), so the initial data passed into useProfileWave reflects the already-known curation id and prevents the selected label/icon from disappearing.
🤖 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/user/waves/UserPageProfileWave.tsx`:
- Around line 301-311: canSwitchOfficialCuration is wrongly gated on curations
being non-empty, which prevents the picker from ever showing its
loading/error/empty/retry states; change its definition to depend only on the
permission/current wave (e.g., set canSwitchOfficialCuration =
canManageOwnOfficialWave or combine with a currentWave flag if present) and
remove the (curations?.length ?? 0) > 0 check, then keep using
isDesktopCurationPickerOpen and shouldRenderMobileCurationPicker as before so
ProfileCurationPicker can receive the curations query and render
loading/error/empty UI; apply the same change wherever canSwitchOfficialCuration
is computed (including the other occurrences mentioned).
- Around line 43-52: The picker lacks a way to clear a selected profile
curation; change onSelectCuration to accept string | null, add a top "No
curation" / "Default wave" row in the ProfileCurationPicker UI that calls
updateProfileWave(profileWaveId, null) (or forwards null via onSelectCuration),
and update all usages (including the occurrences around
ProfileCurationPickerProps, the render block at lines ~137-147, and the handlers
at ~430-435) so they accept and handle null curation ids correctly (e.g., treat
null as the featured/default wave and call updateProfileWave with null). Ensure
types and any dispatch/handler signatures are updated from (curationId: string)
=> void to (curationId: string | null) => void.
In `@hooks/useProfileWave.ts`:
- Around line 26-36: getProfileWaveIdentity currently omits the
ProfileWaveIdentitySource.normalised_handle field from its primary identity
fallback chain, causing profiles with only a normalized handle to fall back to
wallet/address/id; update the fallback order in getProfileWaveIdentity to
include profile?.normalised_handle (insert it before profile?.handle) and keep
calling normalizeProfileWaveIdentity as before so the canonical normalized
handle is returned when present.
---
Outside diff comments:
In `@components/user/waves/UserPageProfileWaveMasonry.tsx`:
- Around line 373-381: The Masonry key currently forces full remounts because it
includes drops.length; update the key generation so it no longer includes
drops.length (keep curationId, containerWidth and masonryTopItemsKey) so masonic
can reconcile via items={masonryItems} and itemKey={(item) =>
item.drop.stableKey}; specifically, modify the masonryKey construction that
references masonryTopItemsKey and drops.length to remove drops.length while
preserving curationId and containerWidth to avoid unnecessary remounts on
append/remove.
---
Duplicate comments:
In `@components/user/waves/UserPageProfileWave.tsx`:
- Around line 252-258: The initialProfileWave useMemo currently sets
profile_curation_id to null causing UI flicker; change the initializer in the
initialProfileWave (the useMemo that returns ApiProfileWaveResponse) to seed
profile_curation_id from resolvedProfile.profile_curation_id when available
(fall back to null only if undefined), so the initial data passed into
useProfileWave reflects the already-known curation id and prevents the selected
label/icon from disappearing.
🪄 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: a567bfaf-1353-4fa7-94de-7f7ee976860e
📒 Files selected for processing (6)
components/user/waves/UserPageProfileWave.tsxcomponents/user/waves/UserPageProfileWaveMasonry.tsxcomponents/user/waves/UserPageProfileWavePickerNonReady.tsxcomponents/user/waves/UserPageProfileWavePickerReady.tsxcomponents/user/waves/UserPageProfileWaveShared.tsxhooks/useProfileWave.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- components/user/waves/UserPageProfileWaveShared.tsx
|



Summary by CodeRabbit
New Features
Improvements