Skip to content

Sidebar 160426#2289

Merged
simo6529 merged 5 commits intomainfrom
sidebar-160426
Apr 17, 2026
Merged

Sidebar 160426#2289
simo6529 merged 5 commits intomainfrom
sidebar-160426

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Apr 17, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Automatic waves sidebar refresh every 60 seconds to keep lists current.
    • Enhanced online recovery—app now syncs data when device reconnects to network.
    • Improved wave pinning with automatic error recovery.
  • Bug Fixes

    • Fixed drop count updates reliability in cached data.
    • Better handling of missing wave data with automatic navigation.
  • Performance

    • Refactored data loading patterns for optimized page rendering.
    • Improved direct message and main waves data refresh strategies.

Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

This PR refactors the MyStream wave UI by splitting MyStreamWave into a Suspense-wrapped wrapper and extracted MyStreamWaveContent component, updates MyStreamContext to add wave registration on active wave selection and browser online/offline sync, adds comprehensive test coverage across components and hooks, modifies React Query cache updates to dynamically scan cache entries, configures periodic refetch intervals for sidebar waves overview, and introduces refactored helper hooks in usePinnedWavesServer.

Changes

Cohort / File(s) Summary
New Test Files - Components
__tests__/components/brain/my-stream/MyStreamWave.register.test.tsx, __tests__/components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.test.ts
Added test suite for MyStreamWave wave registration on direct URL loads with mocked dependencies. Extended increaseWavesOverviewDropsCount tests to cover pinned overview cache updates, infinite-query pagination preservation, and missing wave ID handling.
New Test Files - Contexts & Hooks
__tests__/contexts/wave/MyStreamContext.resume.test.tsx, __tests__/contexts/wave/hooks/useEnhancedWavesListCore.test.tsx, __tests__/hooks/useDmWavesList.test.tsx, __tests__/hooks/usePinnedWavesServer.test.tsx, __tests__/hooks/useWavesList.test.tsx
Added test coverage for MyStreamContext resume/sync behavior (registerWave calls, online event handling, document visibility), useEnhancedWavesListCore new-drop recovery, useDmWavesList sorted waves and refetch config, usePinnedWavesServer window-focus refetch behavior, and useWavesList refetch interval assertion.
Test Updates
__tests__/hooks/useWavesOverview.test.tsx
Reformatted test quotes for consistency, added test case for refetchInterval and refetchIntervalInBackground options validation.
Component Refactoring - MyStream
components/brain/my-stream/MyStreamWave.tsx, components/brain/my-stream/MyStreamWaveContent.tsx
MyStreamWave replaced with thin Suspense-wrapped wrapper forwarding to MyStreamWaveContent. MyStreamWaveContent extracts all wave-data fetching, URL/tab handling, view-mode switching, and conditional rendering of wave sections with curation support.
Layout & Suspense
app/messages/page.tsx, app/waves/waves-page.shared.tsx
Added Suspense boundaries (with fallback={null}) wrapping client components inside HydrationBoundary. Replaced async MessagesPage with sync function and static metadata export.
Context & Hook Updates - Wave Management
contexts/wave/MyStreamContext.tsx, contexts/wave/hooks/useEnhancedWavesListCore.ts
Added ActiveWaveSetOptions type and setActiveWaveAndRegister wrapper to register waves on activeWave.set calls. Added browser "online" event listener for refetch triggers. Split refetch operations into main/DM wave groups. Changed useEnhancedWavesListCore to pass refetchAllWaves instead of mainWavesRefetch to useNewDropCounter.
Hook Updates - Refetch Configuration
hooks/useWavesList.ts, hooks/useDmWavesList.ts, hooks/useWavesOverview.ts
Updated hooks to accept and configure refetchInterval and refetchIntervalInBackground options. Imported and passed SIDEBAR_WAVES_OVERVIEW_REFETCH_INTERVAL_MS (60s) constant to useWavesOverview calls.
Hook Refactoring - Pinned Waves
hooks/usePinnedWavesServer.ts
Major refactoring: extracted helper hooks (usePinnedWavesQueryKey, usePinnedWavesQuery, usePinnedWavesBudget), introduced typed PinnedWavesQueryKey and MutationContext, added refetchOnWindowFocus: "always" config, refactored optimistic pin/unpin logic with cache restoration on error, and tightened ongoing-operation counting by filtering already-pinned IDs.
Cache Utilities
components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.tsx, components/react-query-wrapper/utils/query-utils.ts
Replaced hard-coded query-key building with dynamic cache scanning via queryClient.getQueriesData. Updated in-place cache updates to set last_drop_time and metrics timestamp fields using Math.max consistency. Added exported constant SIDEBAR_WAVES_OVERVIEW_REFETCH_INTERVAL_MS = 60_000.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested reviewers

  • prxt6529
  • ragnep

Poem

🐰 A wave refactored, content extracted clean,
Suspense now wraps where logic once had been,
Caches scan dynamic, drops counted true,
Browser syncs online—the context finds its crew! 🌊✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title 'Sidebar 160426' is vague and does not convey meaningful information about the changeset. It appears to be a branch name or ticket reference rather than a descriptive summary of the changes. Provide a descriptive title that summarizes the main change, such as 'Add wave registration, polling intervals, and Suspense boundaries to sidebar components' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sidebar-160426

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.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 17, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
11 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

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 (11)
hooks/usePinnedWavesServer.ts (2)

211-220: Nit: redundant type predicates on Array.prototype.find.

data / page are already typed as ApiWave[], so (wave): wave is ApiWave => ... adds no narrowing value. A plain predicate keeps the intent clearer.

♻️ Optional simplification
-  if (Array.isArray(data)) {
-    return data.find((wave): wave is ApiWave => wave.id === waveId);
-  }
-
-  for (const page of data.pages) {
-    const wave = page.find((item): item is ApiWave => item.id === waveId);
+  if (Array.isArray(data)) {
+    return data.find((wave) => wave.id === waveId);
+  }
+
+  for (const page of data.pages) {
+    const wave = page.find((item) => item.id === waveId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/usePinnedWavesServer.ts` around lines 211 - 220, The find predicates
use redundant type-guard annotations `(wave): wave is ApiWave => ...` even
though `data` and `page` are already `ApiWave[]`; replace those with simple
boolean predicates like `wave => wave.id === waveId` in both the
Array.isArray(data) branch and the `for (const page of data.pages)` loop
(referencing variables `data`, `page`, `wave`, and `waveId` in this file) to
simplify the code and keep intent clearer.

176-184: Use explicit comparison for pinned filter check.

Boolean(queryParams.pinned) treats any truthy value as "not a main waves query". Currently, only ApiWavesPinFilter.Pinned is used in WAVES_OVERVIEW queries, so no invalidation gap exists in practice. However, for defensive programming and clarity, match explicitly on the Pinned variant:

Suggested refinement
-  if (Boolean(queryParams.pinned)) {
-    return false;
-  }
+  if (queryParams.pinned === ApiWavesPinFilter.Pinned) {
+    return false;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/usePinnedWavesServer.ts` around lines 176 - 184, The pinned filter
check currently uses Boolean(queryParams.pinned) which treats any truthy value
as a match; update the check to explicitly compare against the Pinned enum value
(e.g., queryParams.pinned === ApiWavesPinFilter.Pinned) inside the same function
(usePinnedWavesServer) so only the intended ApiWavesPinFilter.Pinned variant
causes the early return; ensure the ApiWavesPinFilter symbol is
available/imported in the module and keep the rest of the viewer_identity
comparison logic unchanged.
app/waves/waves-page.shared.tsx (1)

138-140: Suspense boundary wrapping is fine; confirm intent of fallback={null}.

With fallback={null}, any future suspending work inside WavesPageClient (e.g., use() of a promise, lazy loading) will show a blank screen rather than a skeleton. That’s acceptable if the client tree is never expected to suspend post-hydration, but worth keeping in mind when extending the subtree.

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

In `@app/waves/waves-page.shared.tsx` around lines 138 - 140, The Suspense wrapper
around WavesPageClient uses fallback={null}, which will render a blank screen if
the client tree suspends; confirm whether that was intentional or replace it
with a visible fallback (e.g., a small LoadingSkeleton or Loader component) so
users see a placeholder during suspense. Locate the Suspense component that
wraps WavesPageClient and either document the intent to keep fallback={null} or
swap fallback={null} for a lightweight presentational component (e.g.,
LoadingSkeleton, WavesPlaceholder) that matches the page’s UX while async work
resolves.
app/messages/page.tsx (1)

15-22: Consider dropping the unused searchParams prop.

Since the component is now synchronous and _searchParams is unused, you could remove the prop entirely to simplify the signature. Next.js 15 still supports page components without props. Keeping it only matters if you plan to consume search params soon; otherwise it’s dead surface.

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

In `@app/messages/page.tsx` around lines 15 - 22, Remove the unused searchParams
prop from the page component signature: update the export default function
MessagesPage declaration to take no parameters (remove the
searchParams/_searchParams argument and its Promise type) and delete the
associated type annotation block; ensure there are no remaining references to
_searchParams inside MessagesPage and adjust any tests or imports that might
have expected the old signature.
__tests__/hooks/useWavesOverview.test.tsx (1)

50-75: LGTM – forwards polling options to useInfiniteQuery.

Good coverage for the new pass-through. One minor nit: you could also add a negative test asserting the defaults (e.g., refetchIntervalInBackground: false when not provided) to lock in the default contract defined in the hook signature.

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

In `@__tests__/hooks/useWavesOverview.test.tsx` around lines 50 - 75, Add a
complementary negative test to ensure useWavesOverview uses the hook defaults
when polling options are omitted: in __tests__/hooks/useWavesOverview.test.tsx
add a test that calls renderHook(() => useWavesOverview({ type:
ApiWavesOverviewType.RecentlyDroppedTo })) (no
refetchInterval/refetchIntervalInBackground) and assert useInfiniteQueryMock was
called with expect.objectContaining({ refetchIntervalInBackground: false }) (and
optionally default refetchInterval value), so the default contract in
useWavesOverview is locked in.
__tests__/contexts/wave/MyStreamContext.resume.test.tsx (1)

94-99: Restore document.visibilityState after the suite to avoid test pollution.

setDocumentVisibilityState mutates the global document via defineProperty and is never reset. Because JSDOM is typically shared across test files in the same worker, leaving it as "hidden" after this suite can silently affect later tests that read document.visibilityState. Consider capturing the original descriptor and restoring it in afterAll, or at least resetting to "visible" in afterEach.

♻️ Proposed fix
 describe("MyStreamProvider resume sync", () => {
   const mainRefetch = jest.fn();
   const dmRefetch = jest.fn();
+  const originalVisibility = Object.getOwnPropertyDescriptor(
+    Document.prototype,
+    "visibilityState"
+  );

   beforeEach(() => {
     jest.clearAllMocks();
     setDocumentVisibilityState("visible");
     useWavesListMock.mockReturnValue(createListData(mainRefetch));
     useDmWavesListMock.mockReturnValue(createListData(dmRefetch));
   });
+
+  afterAll(() => {
+    if (originalVisibility) {
+      Object.defineProperty(document, "visibilityState", originalVisibility);
+    }
+  });

Also applies to: 122-131

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

In `@__tests__/contexts/wave/MyStreamContext.resume.test.tsx` around lines 94 -
99, The helper setDocumentVisibilityState mutates the global
document.visibilityState without restoring it, risking test pollution; capture
the original descriptor (e.g., via Object.getOwnPropertyDescriptor(document,
"visibilityState")) before redefining it in setDocumentVisibilityState and
restore that descriptor in an afterAll (or reset to "visible" in afterEach) so
the global JSDOM state is returned to its prior value; update the test file to
save the original descriptor and call Object.defineProperty(document,
"visibilityState", originalDescriptor) during teardown.
hooks/useWavesOverview.ts (1)

107-125: Minor: the deferred retry can still fire after unmount.

The setTimeout branches invoke query.fetchNextPage() / query.refetch() 30s later with no cleanup, so a stale deferred retry can run after the consumer unmounts or after lastErrorTimestamp resets. TanStack will typically no-op on an unmounted observer, so this is mostly benign; if you want to tighten it, capture the timer id and clear it in a cleanup effect. Not a new issue introduced by this PR, just flagging adjacent to the void additions.

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

In `@hooks/useWavesOverview.ts` around lines 107 - 125, The deferred retry
setTimeouts in the useCallback handlers fetchNextPage and refetch can fire after
the component unmounts; capture the timer IDs when scheduling a retry and store
them in a ref (e.g., retryTimerRef), then clear those timers in a useEffect
cleanup (clearTimeout) to prevent stale callbacks from running; update both
fetchNextPage and refetch to save the timer id returned by setTimeout into the
ref and ensure the cleanup effect clears any existing timer when the component
unmounts or when lastErrorTimestamp/query changes.
components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.tsx (1)

11-29: Minor: include prior your_latest_drop_timestamp in the Math.max.

latestDropTimestamp is computed from timestamp, wave.last_drop_time, and wave.metrics.latest_drop_timestamp, but it is then assigned to your_latest_drop_timestamp without consulting its previous value. If server data or clock skew ever leaves your_latest_drop_timestamp ahead of latest_drop_timestamp, this overwrite can move it backwards. Cheap to harden:

🛡️ Proposed fix
   const latestDropTimestamp = Math.max(
     timestamp,
     wave.last_drop_time,
-    wave.metrics.latest_drop_timestamp
+    wave.metrics.latest_drop_timestamp,
+    wave.metrics.your_latest_drop_timestamp ?? 0
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.tsx`
around lines 11 - 29, The updateWaveDropMetrics function currently computes
latestDropTimestamp using timestamp, wave.last_drop_time and
wave.metrics.latest_drop_timestamp but then unconditionally sets
your_latest_drop_timestamp to that value; include the previous
your_latest_drop_timestamp in the Math.max so we never move the user's latest
drop backwards (i.e., compute latestDropTimestamp = Math.max(timestamp,
wave.last_drop_time, wave.metrics.latest_drop_timestamp,
wave.metrics.your_latest_drop_timestamp) while handling a possible undefined
your_latest_drop_timestamp with a safe default).
__tests__/hooks/useDmWavesList.test.tsx (1)

18-23: Optional: prefer jest.mocked(...) over require(...) as jest.Mock.

jest.mocked(useAuth) (with normal ES imports) gives proper typing and avoids mixing CJS require with ESM imports in a TS test file.

♻️ Proposed refactor
-const useAuthMock = require("@/components/auth/Auth").useAuth as jest.Mock;
-const useSeizeConnectContextMock =
-  require("@/components/auth/SeizeConnectContext")
-    .useSeizeConnectContext as jest.Mock;
-const useWavesOverviewMock = require("@/hooks/useWavesOverview")
-  .useWavesOverview as jest.Mock;
+import { useAuth } from "@/components/auth/Auth";
+import { useSeizeConnectContext } from "@/components/auth/SeizeConnectContext";
+import { useWavesOverview } from "@/hooks/useWavesOverview";
+
+const useAuthMock = jest.mocked(useAuth);
+const useSeizeConnectContextMock = jest.mocked(useSeizeConnectContext);
+const useWavesOverviewMock = jest.mocked(useWavesOverview);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/hooks/useDmWavesList.test.tsx` around lines 18 - 23, Replace the
CommonJS require-as-mock pattern with typed Jest helpers: import the hooks
(useAuth, useSeizeConnectContext, useWavesOverview) via normal ES imports and
then wrap them with jest.mocked(...) to get proper typings instead of using
useAuthMock/useSeizeConnectContextMock/useWavesOverviewMock created via
require(...) as jest.Mock; update any test usages to reference the
jest.mocked(...) instances and ensure the corresponding modules are
jest.mock(...)'d at top of the test file.
components/brain/my-stream/MyStreamWave.tsx (1)

8-14: LGTM — Suspense wrapper is clean.

One optional consideration: fallback={null} means users see an empty panel briefly when useWaveData suspends on initial load. If that’s visible on slower connections, a lightweight skeleton here (or at the parent boundary) would be a nicer UX. Not blocking.

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

In `@components/brain/my-stream/MyStreamWave.tsx` around lines 8 - 14, The
Suspense boundary in the MyStreamWave component currently uses fallback={null},
which can show an empty panel while MyStreamWaveContent (and its useWaveData
hook) suspends; replace the null fallback with a lightweight skeleton or spinner
component (or accept a fallback prop from the parent) so MyStreamWave renders a
brief placeholder during initial load — locate MyStreamWave and swap
fallback={null} for a small Skeleton/Loader component (or thread a fallback prop
through MyStreamWave to keep the boundary flexible).
components/brain/my-stream/MyStreamWaveContent.tsx (1)

116-138: Consider memoizing onDropClick / onSelectCuration with useCallback.

These handlers are recreated on every render and are passed down to multiple child components (MyStreamWaveChat, MyStreamWaveLeaderboard, MyStreamWaveSubmissions, WaveWinners, MyStreamWaveMyVotes, MyStreamWaveCurationContent, and MyStreamWaveTabs). If any of these memoize by prop identity, they’ll re-render unnecessarily. Since the component re-renders whenever waves.list/directMessages.list changes (which is frequent), wrapping in useCallback is worthwhile.

♻️ Proposed change
-  const onDropClick = (drop: ExtendedDrop) => {
+  const onDropClick = useCallback((drop: ExtendedDrop) => {
     queryClient.setQueryData<ApiDrop>(
       [QueryKey.DROP, { drop_id: drop.id }],
       drop as ApiDrop
     );
     const params = new URLSearchParams(searchParams.toString() || "");
     params.set("drop", drop.id);
     router.push(`${pathname}?${params.toString()}`, { scroll: false });
-  };
+  }, [queryClient, searchParams, pathname, router]);

-  const onSelectCuration = (curationId: string | null) => {
+  const onSelectCuration = useCallback((curationId: string | null) => {
     const params = new URLSearchParams(searchParams.toString() || "");
     if (curationId) {
       params.set("curation", curationId);
     } else {
       params.delete("curation");
     }
     const nextQuery = params.toString();
     const nextUrl = nextQuery ? `${pathname}?${nextQuery}` : pathname;
     router.replace(nextUrl, { scroll: false });
-  };
+  }, [searchParams, pathname, router]);

Requires adding useCallback to the React import on line 3.

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

In `@components/brain/my-stream/MyStreamWaveContent.tsx` around lines 116 - 138,
Wrap the onDropClick and onSelectCuration handlers in useCallback and add
useCallback to the React import so their identities are stable across renders;
specifically, convert the functions named onDropClick and onSelectCuration into
callbacks with appropriate dependency arrays (include queryClient, searchParams,
pathname, router for onDropClick and include searchParams, pathname, router for
onSelectCuration) so child components that rely on prop identity won't re-render
unnecessarily.
🤖 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__/contexts/wave/MyStreamContext.resume.test.tsx`:
- Around line 94-99: The helper setDocumentVisibilityState mutates the global
document.visibilityState without restoring it, risking test pollution; capture
the original descriptor (e.g., via Object.getOwnPropertyDescriptor(document,
"visibilityState")) before redefining it in setDocumentVisibilityState and
restore that descriptor in an afterAll (or reset to "visible" in afterEach) so
the global JSDOM state is returned to its prior value; update the test file to
save the original descriptor and call Object.defineProperty(document,
"visibilityState", originalDescriptor) during teardown.

In `@__tests__/hooks/useDmWavesList.test.tsx`:
- Around line 18-23: Replace the CommonJS require-as-mock pattern with typed
Jest helpers: import the hooks (useAuth, useSeizeConnectContext,
useWavesOverview) via normal ES imports and then wrap them with jest.mocked(...)
to get proper typings instead of using
useAuthMock/useSeizeConnectContextMock/useWavesOverviewMock created via
require(...) as jest.Mock; update any test usages to reference the
jest.mocked(...) instances and ensure the corresponding modules are
jest.mock(...)'d at top of the test file.

In `@__tests__/hooks/useWavesOverview.test.tsx`:
- Around line 50-75: Add a complementary negative test to ensure
useWavesOverview uses the hook defaults when polling options are omitted: in
__tests__/hooks/useWavesOverview.test.tsx add a test that calls renderHook(() =>
useWavesOverview({ type: ApiWavesOverviewType.RecentlyDroppedTo })) (no
refetchInterval/refetchIntervalInBackground) and assert useInfiniteQueryMock was
called with expect.objectContaining({ refetchIntervalInBackground: false }) (and
optionally default refetchInterval value), so the default contract in
useWavesOverview is locked in.

In `@app/messages/page.tsx`:
- Around line 15-22: Remove the unused searchParams prop from the page component
signature: update the export default function MessagesPage declaration to take
no parameters (remove the searchParams/_searchParams argument and its Promise
type) and delete the associated type annotation block; ensure there are no
remaining references to _searchParams inside MessagesPage and adjust any tests
or imports that might have expected the old signature.

In `@app/waves/waves-page.shared.tsx`:
- Around line 138-140: The Suspense wrapper around WavesPageClient uses
fallback={null}, which will render a blank screen if the client tree suspends;
confirm whether that was intentional or replace it with a visible fallback
(e.g., a small LoadingSkeleton or Loader component) so users see a placeholder
during suspense. Locate the Suspense component that wraps WavesPageClient and
either document the intent to keep fallback={null} or swap fallback={null} for a
lightweight presentational component (e.g., LoadingSkeleton, WavesPlaceholder)
that matches the page’s UX while async work resolves.

In `@components/brain/my-stream/MyStreamWave.tsx`:
- Around line 8-14: The Suspense boundary in the MyStreamWave component
currently uses fallback={null}, which can show an empty panel while
MyStreamWaveContent (and its useWaveData hook) suspends; replace the null
fallback with a lightweight skeleton or spinner component (or accept a fallback
prop from the parent) so MyStreamWave renders a brief placeholder during initial
load — locate MyStreamWave and swap fallback={null} for a small Skeleton/Loader
component (or thread a fallback prop through MyStreamWave to keep the boundary
flexible).

In `@components/brain/my-stream/MyStreamWaveContent.tsx`:
- Around line 116-138: Wrap the onDropClick and onSelectCuration handlers in
useCallback and add useCallback to the React import so their identities are
stable across renders; specifically, convert the functions named onDropClick and
onSelectCuration into callbacks with appropriate dependency arrays (include
queryClient, searchParams, pathname, router for onDropClick and include
searchParams, pathname, router for onSelectCuration) so child components that
rely on prop identity won't re-render unnecessarily.

In `@components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.tsx`:
- Around line 11-29: The updateWaveDropMetrics function currently computes
latestDropTimestamp using timestamp, wave.last_drop_time and
wave.metrics.latest_drop_timestamp but then unconditionally sets
your_latest_drop_timestamp to that value; include the previous
your_latest_drop_timestamp in the Math.max so we never move the user's latest
drop backwards (i.e., compute latestDropTimestamp = Math.max(timestamp,
wave.last_drop_time, wave.metrics.latest_drop_timestamp,
wave.metrics.your_latest_drop_timestamp) while handling a possible undefined
your_latest_drop_timestamp with a safe default).

In `@hooks/usePinnedWavesServer.ts`:
- Around line 211-220: The find predicates use redundant type-guard annotations
`(wave): wave is ApiWave => ...` even though `data` and `page` are already
`ApiWave[]`; replace those with simple boolean predicates like `wave => wave.id
=== waveId` in both the Array.isArray(data) branch and the `for (const page of
data.pages)` loop (referencing variables `data`, `page`, `wave`, and `waveId` in
this file) to simplify the code and keep intent clearer.
- Around line 176-184: The pinned filter check currently uses
Boolean(queryParams.pinned) which treats any truthy value as a match; update the
check to explicitly compare against the Pinned enum value (e.g.,
queryParams.pinned === ApiWavesPinFilter.Pinned) inside the same function
(usePinnedWavesServer) so only the intended ApiWavesPinFilter.Pinned variant
causes the early return; ensure the ApiWavesPinFilter symbol is
available/imported in the module and keep the rest of the viewer_identity
comparison logic unchanged.

In `@hooks/useWavesOverview.ts`:
- Around line 107-125: The deferred retry setTimeouts in the useCallback
handlers fetchNextPage and refetch can fire after the component unmounts;
capture the timer IDs when scheduling a retry and store them in a ref (e.g.,
retryTimerRef), then clear those timers in a useEffect cleanup (clearTimeout) to
prevent stale callbacks from running; update both fetchNextPage and refetch to
save the timer id returned by setTimeout into the ref and ensure the cleanup
effect clears any existing timer when the component unmounts or when
lastErrorTimestamp/query changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4888dac3-57bf-4ab7-b3a1-528cd8704945

📥 Commits

Reviewing files that changed from the base of the PR and between d8edcc2 and ea63178.

📒 Files selected for processing (20)
  • __tests__/components/brain/my-stream/MyStreamWave.register.test.tsx
  • __tests__/components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.test.ts
  • __tests__/contexts/wave/MyStreamContext.resume.test.tsx
  • __tests__/contexts/wave/hooks/useEnhancedWavesListCore.test.tsx
  • __tests__/hooks/useDmWavesList.test.tsx
  • __tests__/hooks/usePinnedWavesServer.test.tsx
  • __tests__/hooks/useWavesList.test.tsx
  • __tests__/hooks/useWavesOverview.test.tsx
  • app/messages/page.tsx
  • app/waves/waves-page.shared.tsx
  • components/brain/my-stream/MyStreamWave.tsx
  • components/brain/my-stream/MyStreamWaveContent.tsx
  • components/react-query-wrapper/utils/increaseWavesOverviewDropsCount.tsx
  • components/react-query-wrapper/utils/query-utils.ts
  • contexts/wave/MyStreamContext.tsx
  • contexts/wave/hooks/useEnhancedWavesListCore.ts
  • hooks/useDmWavesList.ts
  • hooks/usePinnedWavesServer.ts
  • hooks/useWavesList.ts
  • hooks/useWavesOverview.ts

@simo6529 simo6529 merged commit 593a202 into main Apr 17, 2026
8 checks passed
@simo6529 simo6529 deleted the sidebar-160426 branch April 17, 2026 09:46
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