Skip to content

Reordering tabs and set profile curation#2295

Merged
ragnep merged 10 commits intomainfrom
reordering-tabs
Apr 21, 2026
Merged

Reordering tabs and set profile curation#2295
ragnep merged 10 commits intomainfrom
reordering-tabs

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Apr 20, 2026

Summary by CodeRabbit

  • New Features

    • Drag-and-drop reordering of curations on desktop, plus move up/down actions
    • Profile curation management: set/unset profile curation and a profile curation picker (desktop & mobile)
    • Per-curation action menus: edit, delete (deleting an active curation auto-deselects it)
  • Improvements

    • Tab UI: optional leading icons and per-tab action elements
    • Mobile shows full tab list (no “more” overflow)
    • New CSV download endpoint for subscription counts

ragnep added 2 commits April 20, 2026 10:18
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@ragnep has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 14 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 48 minutes and 14 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: 6cdb1ad1-8fb5-49be-964b-38cd63379fa5

📥 Commits

Reviewing files that changed from the base of the PR and between e18370c and 391c0ce.

📒 Files selected for processing (1)
  • hooks/useProfileWave.ts
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Tab UI & DnD
components/brain/my-stream/MyStreamWaveDesktopTabs.tsx, components/common/TabToggle.tsx
TabOption supports leadingIcon and per-tab action. Desktop curation tabs moved to a custom tablist and rendered with @dnd-kit for drag-and-drop reordering; TabToggle renders sibling action nodes when present.
Curation Tab Menu
components/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsx
New per-curation menu with edit/delete/profile-curation actions, delete mutation with optimistic cache updates, toasts, dialogs, and onDeleted callback.
Profile-Wave UI & Pickers
components/user/waves/UserPageProfileWave.tsx, components/user/waves/UserPageProfileWaveShared.tsx, components/user/waves/UserPageProfileWavePickerReady.tsx, components/user/waves/UserPageProfileWavePickerNonReady.tsx, components/user/waves/UserPageProfileWaveMasonry.tsx
Adds profile curation picker (desktop dropdown + mobile sheet), profile curation label/display in OfficialWaveSummary, copy updates ("official"→"featured"), and masonry key fix to include total drops.
Curation Reorder & Active Controls
hooks/waves/useWaveCurationReorderMutation.ts, hooks/waves/useWaveCurations.ts, components/waves/groups/curation/WaveActiveCurationSection.tsx
New useWaveCurationReorderMutation with optimistic reordering, reorderCuration/moveCuration APIs, added sortWaveCurations, and move up/down actions in active-curation menu gated by permissions and pending state.
ProfileWave Hook & Mutation + API
hooks/useProfileWave.ts, hooks/useProfileWaveMutation.ts, services/api/profile-wave-api.ts
New useProfileWave hook, identity normalization utilities, cache helpers (setProfileWaveQueryData / invalidateProfileWaveQueries), getProfileWave API, and setProfileWave/mutation extended to accept optional profileCurationId.
Other small updates
components/brain/my-stream/MyStreamWaveDesktopTabs.tsx (selection parsing), components/common/TabToggle.tsx (action layout), components/user/waves/userPageProfileWave.helpers.ts
Helpers updated: curation key helpers, getProfileCurationTitle exported, resolveProfileCuration signature changed, and metadata label simplified. Mobile tab overflow removed in MyStreamWave tabs.
OpenAPI & Dependencies
openapi.yaml, package.json
Renamed drops reaction paths to /reaction, added GET /subscriptions/redeemed-memes-counts/download, and added @dnd-kit/* dependencies in package.json.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • simo6529
  • prxt6529

Poem

🐰 I nudged the tabs and watched them glide,

Curations hop into their ordered stride.
A click, a toast, a profile's little crown,
Dragged into place — the rabbit beams, not down.
Carrots for code, may bugs be few and shy.

🚥 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 title accurately reflects the two main features introduced in the PR: tab reordering functionality (via @dnd-kit integration in MyStreamWaveDesktopTabs and WaveActiveCurationSection) and profile curation selection (new UI in UserPageProfileWave and supporting hooks/mutations).
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 reordering-tabs

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.

ragnep and others added 4 commits April 20, 2026 14:21
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
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 (2)
openapi.yaml (1)

1591-1607: ⚠️ Potential issue | 🟠 Major

Align the DELETE reaction contract with the caller.

hooks/drops/useDropReaction.ts removes reactions via commonApiDelete({ endpoint }) without a body, but this spec requires ApiAddReactionToDropRequest for 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 | 🟠 Major

Synchronize profile identity key precedence with userPageProfileWave.helpers.ts.

The mutation uses query > handle > primary_wallet > id for identity resolution, but the profile page helper uses handle > 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: Prefer slice over replace for prefix stripping.

String.prototype.replace with a string pattern only replaces the first occurrence, so getCurationIdFromTabKey silently 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 and getCurationTabKey at 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

📥 Commits

Reviewing files that changed from the base of the PR and between ffa190f and 2b952b3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
  • components/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsx
  • components/common/TabToggle.tsx
  • components/user/waves/UserPageProfileWave.tsx
  • components/user/waves/UserPageProfileWaveShared.tsx
  • components/user/waves/userPageProfileWave.helpers.ts
  • components/waves/groups/curation/WaveActiveCurationSection.tsx
  • hooks/useProfileWave.ts
  • hooks/useProfileWaveMutation.ts
  • hooks/waves/useWaveCurationReorderMutation.ts
  • hooks/waves/useWaveCurations.ts
  • openapi.yaml
  • package.json
  • services/api/profile-wave-api.ts

Comment thread components/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsx
Comment thread components/common/TabToggle.tsx Outdated
Comment thread components/user/waves/UserPageProfileWave.tsx
Comment thread hooks/useProfileWave.ts Outdated
Comment thread hooks/waves/useWaveCurationReorderMutation.ts
ragnep added 2 commits April 20, 2026 16:30
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2b952b3 and 308b023.

📒 Files selected for processing (6)
  • components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
  • components/brain/my-stream/tabs/MyStreamWaveCurationTabMenu.tsx
  • components/common/TabToggle.tsx
  • hooks/useProfileWave.ts
  • hooks/useProfileWaveMutation.ts
  • hooks/waves/useWaveCurationReorderMutation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • hooks/useProfileWaveMutation.ts
  • hooks/useProfileWave.ts

Comment thread components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
Comment thread components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
Comment thread components/common/TabToggle.tsx
Signed-off-by: ragnep <ragneinfo@gmail.com>
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: 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 | 🟠 Major

Avoid remounting Masonry on every page append/remove.

Line 381 includes drops.length in the masonryKey, 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} and itemKey={(item) => item.drop.stableKey}, masonic can reconcile added/removed drops incrementally. The masonryTopItemsKey (first 8 drops) provides additional identity tracking for the visible top of the grid. Consider removing drops.length from 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 | 🟡 Minor

Seed the initial curation id when it is already available.

profile_curation_id is still initialized as null, so the selected curation label/icon can briefly disappear until useProfileWave refetches. If resolvedProfile carries 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

📥 Commits

Reviewing files that changed from the base of the PR and between 308b023 and e18370c.

📒 Files selected for processing (6)
  • components/user/waves/UserPageProfileWave.tsx
  • components/user/waves/UserPageProfileWaveMasonry.tsx
  • components/user/waves/UserPageProfileWavePickerNonReady.tsx
  • components/user/waves/UserPageProfileWavePickerReady.tsx
  • components/user/waves/UserPageProfileWaveShared.tsx
  • hooks/useProfileWave.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/user/waves/UserPageProfileWaveShared.tsx

Comment thread components/user/waves/UserPageProfileWave.tsx
Comment thread components/user/waves/UserPageProfileWave.tsx
Comment thread hooks/useProfileWave.ts
Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

@ragnep ragnep merged commit 28e3e68 into main Apr 21, 2026
8 checks passed
@ragnep ragnep deleted the reordering-tabs branch April 21, 2026 06:12
This was referenced Apr 21, 2026
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