Skip to content

wip#2219

Closed
simo6529 wants to merge 6 commits intomainfrom
update-trigger
Closed

wip#2219
simo6529 wants to merge 6 commits intomainfrom
update-trigger

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Apr 6, 2026

Summary by CodeRabbit

  • New Features

    • Added automatic wave data refresh when browser window regains focus.
    • Improved app foreground/background transition handling with deduplication.
  • Bug Fixes

    • Fixed wave refresh to account for authentication state.
    • Added cooldown mechanism to prevent excessive refresh operations.
  • Tests

    • Expanded coverage for visibility changes and window focus behavior.
    • Added tests for authenticated vs. unauthenticated wave operations.
    • Improved test infrastructure and mock reliability.

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

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

The PR refactors wave management testing infrastructure, introduces reactive window-focus refetching across data hooks, consolidates foreground refresh logic in MyStreamContext using useEffectEvent, implements a cooldown mechanism for paginated queries, and makes pinned-wave operations conditional on authentication state.

Changes

Cohort / File(s) Summary
Test Infrastructure Refactoring
__tests__/contexts/wave/MyStreamContext.test.tsx, __tests__/hooks/useWavesList.test.tsx
Replaced fixed inline mocks with reusable state-driven Jest mocks; introduced renderWithProvider helper; added lifecycle cleanup via beforeEach/afterEach; expanded integration coverage for subscription switching, refresh triggers (visibility, focus, websocket), and capacitor foreground transitions; added tests for authenticated vs unauthenticated refetch behavior.
New Hook Test Coverage
__tests__/hooks/usePinnedWavesServer.test.tsx, __tests__/hooks/useWaveData.test.tsx, __tests__/hooks/useWavesOverview.test.tsx
Added new test file for usePinnedWavesServer query configuration; updated assertions to verify refetchOnWindowFocus and refetchInterval options; added lifecycle cleanup and new tests for error-retry cooldown delays and unmount cleanup behavior.
Window Focus Refetching
hooks/usePinnedWavesServer.ts, hooks/useWaveBoostedDrops.ts, hooks/useWaveData.ts
Added refetchOnWindowFocus configuration ("always", true, "always" respectively) to automatically refetch queries when browser window regains focus.
Context Refresh Consolidation
contexts/wave/MyStreamContext.tsx
Replaced useCallback-based isWaveMuted with useEffectEvent; introduced refreshWaveViewOnForeground helper consolidating foreground behavior (debouncing, re-registration, refetching, counter resets); added global listeners for document.visibilitychange and window.focus events; simplified mobile foreground effect dependency list.
Query Pagination Cooldown
hooks/useWavesOverview.ts
Introduced shared 30-second cooldown mechanism for fetchNextPage and refetch using timeout refs; added cleanup effect to prevent delayed invocations after unmount; timeout scheduling replaces inline error-timestamp checks.
Conditional Pinned Wave Refetch
hooks/useWavesList.ts
Modified refetchAllWaves to conditionally trigger refetchPinnedWaves only when authenticated (isConnectedIdentity is true), with promise rejection suppression.

Sequence Diagram

sequenceDiagram
    actor User as Browser/App
    participant Ctx as MyStreamContext
    participant Handlers as Refresh Handlers
    participant Queries as React Query Hooks
    
    User->>Ctx: Window focus event / Visibility change
    Ctx->>Handlers: refreshWaveViewOnForeground()
    Handlers->>Handlers: Check dedupe cooldown
    alt Cooldown active
        Handlers->>Handlers: Skip refresh
    else Cooldown expired
        Handlers->>Ctx: Conditionally re-register active wave
        Handlers->>Queries: refetch main waves
        Handlers->>Queries: refetch DM waves
        Handlers->>Queries: reset new drops counters
        Queries-->>Handlers: Query state updated
    end
    
    User->>Ctx: Websocket reconnect
    Ctx->>Handlers: refreshWaveViewOnForeground(dedupe, resetNewDrops)
    Handlers->>Queries: Execute refresh with params
    Queries-->>User: Updated wave data
    
    User->>Ctx: App foreground (Capacitor)
    Ctx->>Handlers: refreshWaveViewOnForeground(resetNewDrops: true)
    Handlers->>Queries: refetch + reset counters
    Queries-->>User: Updated UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested reviewers

  • ragnep
  • prxt6529
  • GelatoGenesis

Poem

🐰 A hop through the queries, now faster they fly,
With focus and cooldown when windows say hi,
The refresh events bounce through contexts anew,
Wave data's authenticated—just for the true,
No drops left unheard in this test-covered view!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 'wip' is a vague, non-descriptive term that does not convey meaningful information about the changeset, which includes significant refactoring of wave context testing, hook updates for query configuration, and refresh trigger improvements. Replace 'wip' with a descriptive title that summarizes the main changes, such as 'Refactor wave data refresh triggers and query configuration' or a similar title that reflects the primary objectives.
✅ 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 update-trigger

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa513f49b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const isWaveMuted = useCallback((waveId: string): boolean => {
const wave = wavesRef.current.find((w) => w.id === waveId);
const isWaveMuted = useEffectEvent((waveId: string): boolean => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep isWaveMuted referentially stable

useEffectEvent returns a new callable each render, so replacing isWaveMuted with it makes this prop unstable. In useWaveRealtimeUpdater, processIncomingDrop depends on isWaveMuted, and that callback is then passed to useWebSocketMessage, whose effect re-subscribes on callback identity changes. When MyStreamProvider re-renders frequently, DROP websocket subscriptions are repeatedly torn down/re-added, creating small windows where incoming realtime messages can be missed.

Useful? React with 👍 / 👎.

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

🧹 Nitpick comments (3)
__tests__/contexts/wave/MyStreamContext.test.tsx (3)

428-434: Rerender with new QueryClient doesn't preserve component state.

Similar to the earlier test, creating a new QueryClient in the rerender doesn't simulate a Capacitor foreground transition accurately—it remounts everything. The reset mocks are called because of mount effects, not because of the state transition being detected.

For accurate behavior testing, preserve the QueryClient:

♻️ Suggested fix
-  const { rerender } = renderWithProvider(<div>child</div>);
+  const queryClient = new QueryClient({
+    defaultOptions: { queries: { retry: false } },
+  });
+
+  const { rerender } = render(
+    <QueryClientProvider client={queryClient}>
+      <MyStreamProvider>
+        <div>child</div>
+      </MyStreamProvider>
+    </QueryClientProvider>
+  );

   mockRegisterWave.mockClear();
   // ... other mock clears ...

   mockCapacitorState = { isCapacitor: true, isActive: true };

   rerender(
-    <QueryClientProvider client={new QueryClient()}>
+    <QueryClientProvider client={queryClient}>
       <MyStreamProvider>
         <div>child</div>
       </MyStreamProvider>
     </QueryClientProvider>
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/contexts/wave/MyStreamContext.test.tsx` around lines 428 - 434, The
test is remounting components by creating a new QueryClient in the rerender, so
mount effects run and mocks reset; instead preserve the original QueryClient
instance across rerenders to simulate a foreground transition. Modify the test
to reuse the QueryClient created before the initial render (keep the QueryClient
variable outside and pass the same instance into QueryClientProvider during
rerender) so that MyStreamProvider and its state are not remounted and the
transition behavior is tested accurately.

153-161: Configure QueryClient for test stability.

Creating QueryClient without disabling retries can cause flaky tests if queries fail intermittently. Consider configuring defaults optimized for testing:

♻️ Suggested improvement
 const renderWithProvider = (children: React.ReactNode = null) => {
-  const queryClient = new QueryClient();
+  const queryClient = new QueryClient({
+    defaultOptions: {
+      queries: {
+        retry: false,
+      },
+    },
+  });

   return render(
     <QueryClientProvider client={queryClient}>
       <MyStreamProvider>{children}</MyStreamProvider>
     </QueryClientProvider>
   );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/contexts/wave/MyStreamContext.test.tsx` around lines 153 - 161,
renderWithProvider constructs a QueryClient without test-friendly options which
can make tests flaky; update the creation of QueryClient inside
renderWithProvider to pass defaultOptions that disable retries for
queries/mutations (e.g., retry: false) and consider setting cacheTime/staleTime
or using a silent logger if needed for tests, so change the QueryClient
instantiation referenced in renderWithProvider to include these testing defaults
before wrapping children with QueryClientProvider and MyStreamProvider.

222-228: Rerender with new QueryClient remounts the entire tree.

Creating a new QueryClient during rerender breaks the continuity of the component tree—it effectively unmounts and remounts everything rather than simulating a prop change. While mockUnsubscribe will still be called (due to unmount cleanup), this doesn't accurately test the behavior of changing waveId while the context persists.

Consider preserving the original QueryClient across rerenders:

♻️ Suggested fix
-  const { rerender } = renderWithProvider(<Messages waveId="wave" />);
+  const queryClient = new QueryClient({
+    defaultOptions: { queries: { retry: false } },
+  });
+
+  const { rerender } = render(
+    <QueryClientProvider client={queryClient}>
+      <MyStreamProvider>
+        <Messages waveId="wave" />
+      </MyStreamProvider>
+    </QueryClientProvider>
+  );

   expect(screen.getByText("a")).toBeInTheDocument();

   act(() => listeners.wave({ id: "wave", drops: ["b"] }));
   expect(screen.getByText("b")).toBeInTheDocument();

   rerender(
-    <QueryClientProvider client={new QueryClient()}>
+    <QueryClientProvider client={queryClient}>
       <MyStreamProvider>
         <Messages waveId="other" />
       </MyStreamProvider>
     </QueryClientProvider>
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/contexts/wave/MyStreamContext.test.tsx` around lines 222 - 228, The
test is remounting the tree by creating a new QueryClient inside rerender, so
change the test to reuse the original QueryClient instance instead of calling
new QueryClient() during rerender; create a single QueryClient before the
initial render (used by QueryClientProvider) and pass that same instance into
the rerender call so QueryClientProvider, MyStreamProvider and Messages (and
their mockUnsubscribe behavior) remain mounted and only the waveId prop changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useWaveBoostedDrops.ts`:
- Around line 42-44: In the useWaveBoostedDrops hook update the react-query
config so window-focus refetching matches the other hooks: change
refetchOnWindowFocus from true to "always" in the query options (alongside
STALE_TIME and REFETCH_INTERVAL) so the hook will immediately refetch on tab
focus rather than only when the cache is stale.

In `@hooks/useWavesOverview.ts`:
- Around line 131-145: The runWithCooldown function can allow a pending timeout
to fire after you immediately call action() when remainingCooldown === 0; fix
this by clearing any pending timeout before the immediate branch: call
clearScheduledAction(timeoutRef) (and ensure timeoutRef.current is set to null
by that helper) right before invoking action() in the remainingCooldown === 0
case so the old scheduled timer cannot later trigger a duplicate
refetch/fetchNextPage.
- Around line 156-170: The current fetchNextPage and refetch callbacks wrap
calls with "void query.fetchNextPage().catch(...)" and "void
query.refetch().catch(...)" which discards the Promise and breaks callers that
await these exported hook methods; change both callbacks (fetchNextPage and
refetch) to return the Promise instead by removing the void and returning the
result of runWithCooldown(...) so the inner arrow returns
query.fetchNextPage().catch(() => { /* keep existing comment */ }) and
query.refetch().catch(() => { /* keep existing comment */ }), ensuring the
useCallback wrappers (fetchNextPage and refetch) now return Promise<void> and
remain compatible with callers awaiting them; keep references to
fetchNextPageTimeoutRef, refetchTimeoutRef, runWithCooldown, and query
unchanged.

---

Nitpick comments:
In `@__tests__/contexts/wave/MyStreamContext.test.tsx`:
- Around line 428-434: The test is remounting components by creating a new
QueryClient in the rerender, so mount effects run and mocks reset; instead
preserve the original QueryClient instance across rerenders to simulate a
foreground transition. Modify the test to reuse the QueryClient created before
the initial render (keep the QueryClient variable outside and pass the same
instance into QueryClientProvider during rerender) so that MyStreamProvider and
its state are not remounted and the transition behavior is tested accurately.
- Around line 153-161: renderWithProvider constructs a QueryClient without
test-friendly options which can make tests flaky; update the creation of
QueryClient inside renderWithProvider to pass defaultOptions that disable
retries for queries/mutations (e.g., retry: false) and consider setting
cacheTime/staleTime or using a silent logger if needed for tests, so change the
QueryClient instantiation referenced in renderWithProvider to include these
testing defaults before wrapping children with QueryClientProvider and
MyStreamProvider.
- Around line 222-228: The test is remounting the tree by creating a new
QueryClient inside rerender, so change the test to reuse the original
QueryClient instance instead of calling new QueryClient() during rerender;
create a single QueryClient before the initial render (used by
QueryClientProvider) and pass that same instance into the rerender call so
QueryClientProvider, MyStreamProvider and Messages (and their mockUnsubscribe
behavior) remain mounted and only the waveId prop changes.
🪄 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: 709a4056-c12e-4018-903a-191bfbe3acd4

📥 Commits

Reviewing files that changed from the base of the PR and between 97d225e and aa513f4.

📒 Files selected for processing (10)
  • __tests__/contexts/wave/MyStreamContext.test.tsx
  • __tests__/hooks/usePinnedWavesServer.test.tsx
  • __tests__/hooks/useWaveData.test.tsx
  • __tests__/hooks/useWavesOverview.test.tsx
  • components/user/layout/userPageVisibility.ts
  • contexts/wave/MyStreamContext.tsx
  • hooks/usePinnedWavesServer.ts
  • hooks/useWaveBoostedDrops.ts
  • hooks/useWaveData.ts
  • hooks/useWavesOverview.ts

Comment on lines 42 to +44
staleTime: STALE_TIME,
refetchInterval: REFETCH_INTERVAL,
refetchOnWindowFocus: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and read the useWaveBoostedDrops.ts file
find . -name "useWaveBoostedDrops.ts" -type f

Repository: 6529-Collections/6529seize-frontend

Length of output: 109


🏁 Script executed:

# List files in hooks directory to confirm structure
git ls-files | grep -E "hooks/.*\.ts" | head -20

Repository: 6529-Collections/6529seize-frontend

Length of output: 1547


🏁 Script executed:

# Read the useWaveBoostedDrops.ts file
cat -n ./hooks/useWaveBoostedDrops.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 1940


🏁 Script executed:

# Search for STALE_TIME and REFETCH_INTERVAL constants
rg "STALE_TIME|REFETCH_INTERVAL" --no-heading -n

Repository: 6529-Collections/6529seize-frontend

Length of output: 3547


🏁 Script executed:

# Search for other uses of refetchOnWindowFocus in the codebase to check for "always" pattern
rg "refetchOnWindowFocus" -A 1 -B 1

Repository: 6529-Collections/6529seize-frontend

Length of output: 5287


🏁 Script executed:

# Check if this file was modified in recent commits to understand PR context
git log --oneline --all -- "hooks/useWaveBoostedDrops.ts" | head -5

Repository: 6529-Collections/6529seize-frontend

Length of output: 157


🏁 Script executed:

# Check if usePinnedWavesServer was recently added/modified to understand if "always" is new in this PR
git log --oneline --all -- "hooks/usePinnedWavesServer.ts" | head -5

Repository: 6529-Collections/6529seize-frontend

Length of output: 279


🏁 Script executed:

# Check useWaveData for context on when "always" was added
git log --oneline --all -- "hooks/useWaveData.ts" | head -5

Repository: 6529-Collections/6529seize-frontend

Length of output: 267


🏁 Script executed:

# Check what changed in the wip commit for usePinnedWavesServer
git show aa513f49:hooks/usePinnedWavesServer.ts 2>/dev/null | grep -A 2 "refetchOnWindowFocus" || echo "File not in wip commit or error"

Repository: 6529-Collections/6529seize-frontend

Length of output: 121


🏁 Script executed:

# Check what changed in the wip commit for useWaveData
git show aa513f49:hooks/useWaveData.ts 2>/dev/null | grep -A 2 "refetchOnWindowFocus" || echo "File not in wip commit or error"

Repository: 6529-Collections/6529seize-frontend

Length of output: 228


🏁 Script executed:

# Look at the actual diff for the current changes
git diff HEAD -- hooks/useWaveBoostedDrops.ts hooks/useWaveData.ts hooks/usePinnedWavesServer.ts 2>/dev/null || git diff aa513f49^..aa513f49 -- hooks/useWaveBoostedDrops.ts hooks/useWaveData.ts hooks/usePinnedWavesServer.ts 2>/dev/null || echo "Check git status"

Repository: 6529-Collections/6529seize-frontend

Length of output: 61


🏁 Script executed:

# Check git status and recent changes
git status --short

Repository: 6529-Collections/6529seize-frontend

Length of output: 92


🏁 Script executed:

# Check the state of useWaveBoostedDrops in the wip commit
git show aa513f49:hooks/useWaveBoostedDrops.ts | grep -A 3 "refetchOnWindowFocus"

Repository: 6529-Collections/6529seize-frontend

Length of output: 185


🏁 Script executed:

# Check if there are any test files or comments that explain the intended behavior
rg "refetchOnWindowFocus.*always" -B 5 -A 2

Repository: 6529-Collections/6529seize-frontend

Length of output: 2065


🏁 Script executed:

# Look at the PR or commit message for context on this change
git log -1 aa513f49 --format=fuller

Repository: 6529-Collections/6529seize-frontend

Length of output: 327


refetchOnWindowFocus: true only refetches stale queries; consider using "always" for consistency with other hooks in this PR.

Line 44 only refetches when data is stale. With staleTime: 60000, a tab restored within that minute will keep showing cached boosted drops until the next 30s polling interval fires. Other hooks in this PR (useWaveData, usePinnedWavesServer) use refetchOnWindowFocus: "always" for immediate foreground freshness—this should match that pattern.

Suggested change
-    refetchOnWindowFocus: true,
+    refetchOnWindowFocus: "always",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
staleTime: STALE_TIME,
refetchInterval: REFETCH_INTERVAL,
refetchOnWindowFocus: true,
staleTime: STALE_TIME,
refetchInterval: REFETCH_INTERVAL,
refetchOnWindowFocus: "always",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useWaveBoostedDrops.ts` around lines 42 - 44, In the
useWaveBoostedDrops hook update the react-query config so window-focus
refetching matches the other hooks: change refetchOnWindowFocus from true to
"always" in the query options (alongside STALE_TIME and REFETCH_INTERVAL) so the
hook will immediately refetch on tab focus rather than only when the cache is
stale.

Comment thread hooks/useWavesOverview.ts
Comment on lines +131 to +145
const runWithCooldown = useCallback(
(timeoutRef: TimeoutRef, action: () => void) => {
const remainingCooldown = getRemainingCooldown();
if (remainingCooldown === 0) {
action();
return;
}

clearScheduledAction(timeoutRef);

timeoutRef.current = setTimeout(() => {
timeoutRef.current = null;
action();
}, remainingCooldown);
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear any pending timeout before the immediate branch.

If a cooldown timer was scheduled while the tab was backgrounded, browser timer throttling can delay it past the cooldown boundary. A later call then runs action() immediately, but the old timeout is still live and can fire afterward, producing a duplicate refetch() / fetchNextPage().

Suggested change
   const runWithCooldown = useCallback(
     (timeoutRef: TimeoutRef, action: () => void) => {
+      clearScheduledAction(timeoutRef);
       const remainingCooldown = getRemainingCooldown();
       if (remainingCooldown === 0) {
         action();
         return;
       }
-
-      clearScheduledAction(timeoutRef);
       timeoutRef.current = setTimeout(() => {
         timeoutRef.current = null;
         action();
       }, remainingCooldown);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const runWithCooldown = useCallback(
(timeoutRef: TimeoutRef, action: () => void) => {
const remainingCooldown = getRemainingCooldown();
if (remainingCooldown === 0) {
action();
return;
}
clearScheduledAction(timeoutRef);
timeoutRef.current = setTimeout(() => {
timeoutRef.current = null;
action();
}, remainingCooldown);
},
const runWithCooldown = useCallback(
(timeoutRef: TimeoutRef, action: () => void) => {
clearScheduledAction(timeoutRef);
const remainingCooldown = getRemainingCooldown();
if (remainingCooldown === 0) {
action();
return;
}
timeoutRef.current = setTimeout(() => {
timeoutRef.current = null;
action();
}, remainingCooldown);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useWavesOverview.ts` around lines 131 - 145, The runWithCooldown
function can allow a pending timeout to fire after you immediately call action()
when remainingCooldown === 0; fix this by clearing any pending timeout before
the immediate branch: call clearScheduledAction(timeoutRef) (and ensure
timeoutRef.current is set to null by that helper) right before invoking action()
in the remainingCooldown === 0 case so the old scheduled timer cannot later
trigger a duplicate refetch/fetchNextPage.

Comment thread hooks/useWavesOverview.ts
Comment on lines +156 to +170
const fetchNextPage = useCallback(() => {
runWithCooldown(fetchNextPageTimeoutRef, () => {
void query.fetchNextPage().catch(() => {
// Error surfaced via query state
});
});
}, [query, runWithCooldown]);

const refetch = useCallback(() => {
runWithCooldown(refetchTimeoutRef, () => {
void query.refetch().catch(() => {
// Error surfaced via query state
});
});
}, [query, runWithCooldown]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -t f "useWavesOverview.ts"

Repository: 6529-Collections/6529seize-frontend

Length of output: 104


🏁 Script executed:

wc -l hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 108


🏁 Script executed:

sed -n '150,185p' hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 1024


🏁 Script executed:

head -n 20 hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 987


🏁 Script executed:

sed -n '1,50p' hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 1789


🏁 Script executed:

sed -n '170,185p' hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 444


🏁 Script executed:

rg -A 20 "export const useWavesOverview" hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 674


🏁 Script executed:

sed -n '30,100p' hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 2365


🏁 Script executed:

rg "useWavesOverview" --type ts --type tsx -B 2 -A 5 | head -100

Repository: 6529-Collections/6529seize-frontend

Length of output: 106


🏁 Script executed:

rg "useWavesOverview" -A 5 -B 2 | head -150

Repository: 6529-Collections/6529seize-frontend

Length of output: 7151


🏁 Script executed:

rg "await.*fetchNextPage\|await.*refetch" --max-count 20

Repository: 6529-Collections/6529seize-frontend

Length of output: 61


🏁 Script executed:

rg "fetchNextPage\|refetch" hooks/ -B 3 -A 3 | grep -v "^--$" | head -200

Repository: 6529-Collections/6529seize-frontend

Length of output: 61


🏁 Script executed:

rg "fetchNextPage" -A 2 -B 2 | head -100

Repository: 6529-Collections/6529seize-frontend

Length of output: 4286


🏁 Script executed:

cat types/waves.types.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 4912


🏁 Script executed:

rg "WaveOutcomeDistributionState\|useWavesOverview.*return\|interface.*Overview" types/waves.types.ts -A 5

Repository: 6529-Collections/6529seize-frontend

Length of output: 61


🏁 Script executed:

sed -n '170,185p' hooks/useWavesOverview.ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 444


Make fetchNextPage() and refetch() awaitable by removing the void wrapping.

The methods are wrapped with void query.fetchNextPage().catch(...) and void query.refetch().catch(...), which discards the promise and converts the return type from Promise<void> to void. This silently breaks the API for callers who await these methods—they will continue immediately instead of waiting for the operation to complete. Code in the codebase (e.g., useWaveDropsLeaderboard.ts) already attempts to await fetchNextPage(), making this a breaking change for an exported hook.

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

In `@hooks/useWavesOverview.ts` around lines 156 - 170, The current fetchNextPage
and refetch callbacks wrap calls with "void query.fetchNextPage().catch(...)"
and "void query.refetch().catch(...)" which discards the Promise and breaks
callers that await these exported hook methods; change both callbacks
(fetchNextPage and refetch) to return the Promise instead by removing the void
and returning the result of runWithCooldown(...) so the inner arrow returns
query.fetchNextPage().catch(() => { /* keep existing comment */ }) and
query.refetch().catch(() => { /* keep existing comment */ }), ensuring the
useCallback wrappers (fetchNextPage and refetch) now return Promise<void> and
remain compatible with callers awaiting them; keep references to
fetchNextPageTimeoutRef, refetchTimeoutRef, runWithCooldown, and query
unchanged.

simo6529 added 5 commits April 6, 2026 17:06
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 7, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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

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

Inline comments:
In `@__tests__/contexts/wave/MyStreamContext.test.tsx`:
- Around line 442-473: The test mounts a fresh QueryClient on rerender so
prevIsActiveRef resets and the foreground-transition branch never runs; fix by
creating a single QueryClient instance before the first render and reuse that
same instance for both the initial render and the rerender (i.e., const qc = new
QueryClient(); use <QueryClientProvider client={qc}> around MyStreamProvider on
initial render and again in rerender) so prevIsActiveRef in MyStreamProvider
correctly sees the previous isActive value and the effect runs.
🪄 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: 69806bcd-e952-4124-bd96-b68b4f7c1e1f

📥 Commits

Reviewing files that changed from the base of the PR and between aa513f4 and 3377f6e.

📒 Files selected for processing (5)
  • __tests__/contexts/wave/MyStreamContext.test.tsx
  • __tests__/hooks/usePinnedWavesServer.test.tsx
  • __tests__/hooks/useWavesList.test.tsx
  • contexts/wave/MyStreamContext.tsx
  • hooks/useWavesList.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/hooks/usePinnedWavesServer.test.tsx

Comment on lines +442 to 473
it("resets all new-drop counters when the native app returns to foreground", () => {
mockActiveWaveId = "wave-1";
mockCapacitorState = { isCapacitor: true, isActive: false };

const { rerender } = renderWithProvider(<div>child</div>);

mockRegisterWave.mockClear();
mockMainWavesRefetch.mockClear();
mockDmMainWavesRefetch.mockClear();
mockRefetchAllWaves.mockClear();
mockRefetchAllDmWaves.mockClear();
mockResetMainWavesNewDropsCount.mockClear();
mockResetDmWavesNewDropsCount.mockClear();

mockCapacitorState = { isCapacitor: true, isActive: true };

rerender(
<QueryClientProvider client={new QueryClient()}>
<MyStreamProvider>
<div>child</div>
</MyStreamProvider>
</QueryClientProvider>
);

expect(unsubscribe).toHaveBeenCalled();
expect(mockRegisterWave).toHaveBeenCalledWith("wave-1", true);
expect(mockRefetchAllWaves).toHaveBeenCalledTimes(1);
expect(mockRefetchAllDmWaves).toHaveBeenCalledTimes(1);
expect(mockMainWavesRefetch).not.toHaveBeenCalled();
expect(mockDmMainWavesRefetch).not.toHaveBeenCalled();
expect(mockResetMainWavesNewDropsCount).toHaveBeenCalledTimes(1);
expect(mockResetDmWavesNewDropsCount).toHaveBeenCalledTimes(1);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if the test passes with the current implementation by checking if similar patterns exist elsewhere
# and examining how other tests handle the rerender scenario

# Check how other tests in this file handle rerenders with QueryClient
rg -n "rerender.*QueryClient" __tests__/contexts/wave/MyStreamContext.test.tsx -A2 -B2

Repository: 6529-Collections/6529seize-frontend

Length of output: 61


🏁 Script executed:

# First, let's check the test file around the specified lines
cat -n __tests__/contexts/wave/MyStreamContext.test.tsx | sed -n '440,475p'

Repository: 6529-Collections/6529seize-frontend

Length of output: 1567


🏁 Script executed:

# Check the MyStreamProvider implementation
find . -name "MyStreamContext.tsx" -o -name "MyStreamProvider.tsx" -o -name "*MyStream*" -type f | grep -v node_modules | grep -v __tests__

Repository: 6529-Collections/6529seize-frontend

Length of output: 1214


🏁 Script executed:

# Look for the MyStreamProvider implementation and its effect/ref usage
rg -n "prevIsActiveRef" . --type tsx --type ts

Repository: 6529-Collections/6529seize-frontend

Length of output: 106


🏁 Script executed:

# Search for prevIsActiveRef without file type restrictions
rg -n "prevIsActiveRef" .

Repository: 6529-Collections/6529seize-frontend

Length of output: 330


🏁 Script executed:

# Examine the MyStreamContext.tsx file to understand the provider implementation
cat -n ./contexts/wave/MyStreamContext.tsx | head -100

Repository: 6529-Collections/6529seize-frontend

Length of output: 4380


🏁 Script executed:

# Check how renderWithProvider is implemented
rg -n "renderWithProvider" __tests__/contexts/wave/MyStreamContext.test.tsx -B5 | head -30

Repository: 6529-Collections/6529seize-frontend

Length of output: 981


🏁 Script executed:

# Get full renderWithProvider implementation
cat -n __tests__/contexts/wave/MyStreamContext.test.tsx | sed -n '153,180p'

Repository: 6529-Collections/6529seize-frontend

Length of output: 1136


🏁 Script executed:

# Check the full effect logic in MyStreamContext around the condition
cat -n ./contexts/wave/MyStreamContext.tsx | sed -n '120,260p'

Repository: 6529-Collections/6529seize-frontend

Length of output: 5190


🏁 Script executed:

# Look at how other tests handle state transitions and rerenders
cat -n __tests__/contexts/wave/MyStreamContext.test.tsx | sed -n '200,240p'

Repository: 6529-Collections/6529seize-frontend

Length of output: 1655


🏁 Script executed:

# Check if there are other tests that test Capacitor state transitions
rg -n "mockCapacitorState.*isActive" __tests__/contexts/wave/MyStreamContext.test.tsx -B3 -A10

Repository: 6529-Collections/6529seize-frontend

Length of output: 2101


Fix the Capacitor foreground transition test to use the same QueryClient instance across rerenders.

The test creates a new QueryClient on rerender (line 459), which mounts a fresh MyStreamProvider instance. This causes prevIsActiveRef to initialize with the current isActive value (true), making the condition !prevIsActiveRef.current && isActive evaluate to false and skipping the effect body.

Keep the same QueryClient instance:

Diff
+  const queryClient = new QueryClient();
   const { rerender } = renderWithProvider(<div>child</div>);

   mockRegisterWave.mockClear();
   ...
   rerender(
-    <QueryClientProvider client={new QueryClient()}>
+    <QueryClientProvider client={queryClient}>
       <MyStreamProvider>
         <div>child</div>
       </MyStreamProvider>
     </QueryClientProvider>
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/contexts/wave/MyStreamContext.test.tsx` around lines 442 - 473, The
test mounts a fresh QueryClient on rerender so prevIsActiveRef resets and the
foreground-transition branch never runs; fix by creating a single QueryClient
instance before the first render and reuse that same instance for both the
initial render and the rerender (i.e., const qc = new QueryClient(); use
<QueryClientProvider client={qc}> around MyStreamProvider on initial render and
again in rerender) so prevIsActiveRef in MyStreamProvider correctly sees the
previous isActive value and the effect runs.

@simo6529 simo6529 closed this Apr 7, 2026
@simo6529 simo6529 deleted the update-trigger branch April 7, 2026 10:35
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.

1 participant