Skip to content

Persist leaderboard after wave is over#2257

Merged
simo6529 merged 8 commits intomainfrom
persist-leaderboard-after-wave-is-over
Apr 14, 2026
Merged

Persist leaderboard after wave is over#2257
simo6529 merged 8 commits intomainfrom
persist-leaderboard-after-wave-is-over

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Apr 13, 2026

Summary by CodeRabbit

  • New Features

    • Added a "Submissions" tab/view for completed waves, showing participant submissions with infinite scroll and integrated drop navigation; mobile now defaults to Submissions for completed rank waves.
  • Bug Fixes

    • Improved URL handling to remove curation filters while preserving other params and avoid unwanted parameter persistence.
    • Submissions view displays a retryable error message when loading fails.
  • Tests

    • Expanded coverage for submissions view, tab/view normalization, mobile behavior, and leaderboard error states.

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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Adds a SUBMISSIONS view and component, wires it into desktop and mobile tab/view logic and defaults, changes content/tab normalization when voting ends, exposes leaderboard error state, refactors time and media-query helpers, and adds/updates tests (one old test file removed).

Changes

Cohort / File(s) Summary
Enums & Types
types/waves.types.ts, components/brain/mobile/brainMobileViews.ts
Added SUBMISSIONS member to MyStreamWaveTab and BrainView.
Tab selection / context
components/brain/ContentTabContext.tsx
Default tab logic now selects SUBMISSIONS when votingState === WaveVotingState.ENDED; tab construction switches between LEADERBOARD and SUBMISSIONS based on ended state.
Mobile active view logic & tests
components/brain/mobile/useBrainMobileActiveView.ts, __tests__/components/brain/mobile/useBrainMobileActiveView.test.ts
Normalize active view to SUBMISSIONS for completed rank waves; updated/reset rules and added tests for wave-loaded behavior.
New submissions feature + tests
components/brain/my-stream/MyStreamWaveSubmissions.tsx, __tests__/components/brain/my-stream/MyStreamWaveSubmissions.test.tsx
New client component rendering submissions with infinite scroll, curation_id cleanup, drop navigation, error/empty/loading states; comprehensive tests for routing and behavior.
Integration points & UI labels
components/brain/my-stream/MyStreamWave.tsx, components/brain/mobile/BrainMobileViewContent.tsx, components/brain/my-stream/MyStreamWaveDesktopTabs.tsx, components/brain/my-stream/MyStreamWaveTabsLeaderboard.tsx
Integrated SUBMISSIONS tab/panel into MyStreamWave and mobile content mapping; desktop tab labels updated; Leaderboard tab now targets SUBMISSIONS when voting completed.
Hooks / exposed state & tests
hooks/useWaveDropsLeaderboard.ts, __tests__/hooks/useWaveDropsLeaderboard.test.ts
Hook now forwards isError from react-query; tests updated to assert error propagation.
Time utilities & rendering
helpers/waves/time.utils.ts, components/waves/leaderboard/WaveLeaderboardTime.tsx, __tests__/helpers/waves/time.utils.test.ts
calculateTimeLeft accepts optional now; leaderboard time component uses a shared currentMillis tick and computes time left at render; tests updated/extended.
Misc. hooks / minor formatting
hooks/useMediaQuery.ts, components/utils/input/identity/IdentitySearch.tsx
Typed matchMedia access and safer listener handling; minor formatting/dep-array change in useMemo.
Tests added/removed/updated
__tests__/components/brain/..., __tests__/components/brain/my-stream/MyStreamWave.test.tsx
Multiple new/updated tests for submissions, mobile lifecycle, tab normalization; removed an obsolete MyStreamWave test file.
Config
knip.jsonc
Added react-doctor to ignored dependencies.

Sequence Diagram

sequenceDiagram
    participant User
    participant TabCtx as ContentTabContext
    participant MyStream as MyStreamWave
    participant Submissions as MyStreamWaveSubmissions
    participant LeaderboardAPI as useWaveDropsLeaderboard

    User->>TabCtx: updateAvailableTabs(votingState = ENDED)
    TabCtx->>TabCtx: compute tabs -> include SUBMISSIONS
    TabCtx->>MyStream: set activeContentTab = SUBMISSIONS

    User->>MyStream: render activeContentTab
    MyStream->>Submissions: mount(wave, onDropClick)
    Submissions->>Submissions: router.replace(remove curation_id)
    Submissions->>LeaderboardAPI: fetch initial pages (wave.id)
    LeaderboardAPI-->>Submissions: return drops / isError / hasNextPage

    User->>Submissions: click drop
    Submissions->>Submissions: router.push(drop=..., remove curation_id)

    User->>Submissions: scroll sentinel visible
    Submissions->>LeaderboardAPI: fetchNextPage()
    LeaderboardAPI-->>Submissions: append more drops
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ragnep

Poem

🐰
I hopped in code to find a view,
When voting ends, submissions bloom anew.
Pages scroll and drops appear,
Routing neat and errors clear.
— a tiny rabbit, cheering you!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: introducing a new 'Submissions' tab to persist and display wave submissions after voting ends, replacing the leaderboard view upon wave completion.

✏️ 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 persist-leaderboard-after-wave-is-over

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: 67bbe11837

ℹ️ 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".

Comment thread components/brain/my-stream/MyStreamWaveSubmissions.tsx
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__/components/brain/mobile/BrainMobileTabs.test.tsx (1)

6-19: Prefer importing BrainView instead of redefining it in test.

Line 10 is correct, but keeping a local enum copy can silently diverge from components/brain/mobile/brainMobileViews.ts.

♻️ Suggested refactor
+import { BrainView } from "@/components/brain/mobile/brainMobileViews";
-
-enum BrainView {
-  DEFAULT = "DEFAULT",
-  ABOUT = "ABOUT",
-  LEADERBOARD = "LEADERBOARD",
-  SUBMISSIONS = "SUBMISSIONS",
-  SALES = "SALES",
-  WINNERS = "WINNERS",
-  OUTCOME = "OUTCOME",
-  MY_VOTES = "MY_VOTES",
-  FAQ = "FAQ",
-  WAVES = "WAVES",
-  MESSAGES = "MESSAGES",
-  NOTIFICATIONS = "NOTIFICATIONS",
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/brain/mobile/BrainMobileTabs.test.tsx` around lines 6 -
19, The test currently redefines the BrainView enum which can drift from the
source; remove the local enum declaration and import BrainView from the
canonical module (components/brain/mobile/brainMobileViews.ts), then update any
references in BrainMobileTabs.test.tsx to use the imported BrainView symbol so
the test always reflects the real implementation.
components/brain/my-stream/MyStreamWaveSubmissions.tsx (2)

56-64: Avoid pushing the same URL repeatedly.

Add a no-op guard when the current drop already matches dropId and curation_id is already absent.

Proposed refactor
   const openDropById = useCallback(
     (dropId: string) => {
       const params = new URLSearchParams(searchParamsString || "");
+      const currentDropId = params.get("drop");
+      const hasCurationFilter = params.has(CURATION_FILTER_PARAM);
+      if (currentDropId === dropId && !hasCurationFilter) {
+        return;
+      }
       params.delete(CURATION_FILTER_PARAM);
       params.set("drop", dropId);
       router.push(`${pathname}?${params.toString()}`, { scroll: false });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx` around lines 56 - 64,
openDropById currently always pushes a new URL even when nothing changes; update
the function (openDropById) to first build URLSearchParams from
searchParamsString, read currentDrop = params.get("drop") and check if
currentDrop === dropId and !params.has(CURATION_FILTER_PARAM); if both true
return early (no-op), otherwise proceed to params.delete(CURATION_FILTER_PARAM),
params.set("drop", dropId) and router.push as before; keep the same dependencies
([pathname, router, searchParamsString]).

112-114: Render the intersection sentinel only when needed.

Once hasNextPage is false, the sentinel can be skipped to reduce unnecessary observer callbacks.

Proposed refactor
-        <div ref={intersectionElementRef}></div>
+        {hasNextPage && <div ref={intersectionElementRef} aria-hidden="true" />}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx` around lines 112 -
114, The sentinel div is always rendered which triggers the intersection
observer even when there are no more pages; update the render to only mount the
sentinel when hasNextPage is true by conditionally rendering the <div
ref={intersectionElementRef}> (keep the existing isFetchingNextPage check for
<WaveLeaderboardLoadingBar /> unchanged) so the observer is only attached while
hasNextPage is true; locate the sentinel near the isFetchingNextPage usage and
change its rendering to depend on hasNextPage and intersectionElementRef.
🤖 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__/components/brain/ContentTabContext.test.tsx`:
- Around line 188-194: The test expectation for result.current.availableTabs is
incorrect for the case where isCurationWave is false; update the assertion in
the test to match buildDefaultTabs behavior by removing MyStreamWaveTab.MY_VOTES
so the expected array is [MyStreamWaveTab.CHAT, MyStreamWaveTab.SUBMISSIONS,
MyStreamWaveTab.WINNERS, MyStreamWaveTab.OUTCOME]; alternatively, if you
intended to assert the curation-wave behavior, set isCurationWave to true before
calling the provider so that MyStreamWaveTab.MY_VOTES is included.

In `@components/brain/mobile/useBrainMobileActiveView.ts`:
- Around line 105-106: The computed waveDefaultView should not pick
BrainView.SUBMISSIONS until the wave payload exists; update the logic around the
waveDefaultView assignment (and the similar block around lines 177-183) to
require a non-null/loaded wave (e.g., check wave !== null or wave.payload
exists) in addition to isRankWave && isCompleted before returning
BrainView.SUBMISSIONS, so normalizeActiveView() won't preserve SUBMISSIONS when
the wave is still loading and BrainMobileViewContent won't render null.

In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx`:
- Around line 35-40: The component currently treats a failed leaderboard fetch
the same as an empty result; update the render logic where
useWaveDropsLeaderboard is used (the destructure with drops, fetchNextPage,
hasNextPage, isFetching, isFetchingNextPage) to check for an error state before
the empty-state branch: use the hook's error/isError (or equivalent) and refetch
(or retry) function to render an explicit error UI with a retry button that
calls refetch, and only fall back to "No submissions to show" when there is no
error and drops is empty; apply the same change for the other occurrence around
lines 85-95 where the leaderboard hook is consumed.

---

Nitpick comments:
In `@__tests__/components/brain/mobile/BrainMobileTabs.test.tsx`:
- Around line 6-19: The test currently redefines the BrainView enum which can
drift from the source; remove the local enum declaration and import BrainView
from the canonical module (components/brain/mobile/brainMobileViews.ts), then
update any references in BrainMobileTabs.test.tsx to use the imported BrainView
symbol so the test always reflects the real implementation.

In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx`:
- Around line 56-64: openDropById currently always pushes a new URL even when
nothing changes; update the function (openDropById) to first build
URLSearchParams from searchParamsString, read currentDrop = params.get("drop")
and check if currentDrop === dropId and !params.has(CURATION_FILTER_PARAM); if
both true return early (no-op), otherwise proceed to
params.delete(CURATION_FILTER_PARAM), params.set("drop", dropId) and router.push
as before; keep the same dependencies ([pathname, router, searchParamsString]).
- Around line 112-114: The sentinel div is always rendered which triggers the
intersection observer even when there are no more pages; update the render to
only mount the sentinel when hasNextPage is true by conditionally rendering the
<div ref={intersectionElementRef}> (keep the existing isFetchingNextPage check
for <WaveLeaderboardLoadingBar /> unchanged) so the observer is only attached
while hasNextPage is true; locate the sentinel near the isFetchingNextPage usage
and change its rendering to depend on hasNextPage and intersectionElementRef.
🪄 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: d66c0128-094b-4d09-8fc5-96e842f2d784

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5d895 and 67bbe11.

📒 Files selected for processing (20)
  • __tests__/components/brain/ContentTabContext.test.tsx
  • __tests__/components/brain/mobile/BrainMobileTabs.test.tsx
  • __tests__/components/brain/mobile/BrainMobileViewContent.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWave.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveSubmissions.test.tsx
  • __tests__/components/brain/my-stream/MyStreamWaveTabsLeaderboard.test.tsx
  • __tests__/helpers/waves/time.utils.test.ts
  • components/brain/ContentTabContext.tsx
  • components/brain/mobile/BrainMobileViewContent.tsx
  • components/brain/mobile/brainMobileViews.ts
  • components/brain/mobile/useBrainMobileActiveView.ts
  • components/brain/my-stream/MyStreamWave.tsx
  • components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
  • components/brain/my-stream/MyStreamWaveSubmissions.tsx
  • components/brain/my-stream/MyStreamWaveTabsLeaderboard.tsx
  • components/utils/input/identity/IdentitySearch.tsx
  • components/waves/leaderboard/WaveLeaderboardTime.tsx
  • helpers/waves/time.utils.ts
  • hooks/useMediaQuery.ts
  • types/waves.types.ts
💤 Files with no reviewable changes (1)
  • tests/components/brain/my-stream/MyStreamWave.test.tsx

Comment thread __tests__/components/brain/ContentTabContext.test.tsx
Comment thread components/brain/mobile/useBrainMobileActiveView.ts Outdated
Comment thread components/brain/my-stream/MyStreamWaveSubmissions.tsx Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 `@components/brain/my-stream/MyStreamWaveSubmissions.tsx`:
- Around line 49-51: Replace the uses of the void operator that suppress promise
rejections with explicit no-op catch handlers: in handleRetry, change void
refetch() to refetch().catch(() => undefined) (referencing handleRetry and
refetch), and apply the same pattern to the other promise call that currently
uses void on line ~89 — replace that void <promiseCall>() with
<promiseCall>().catch(() => undefined) so any rejection is explicitly handled.
🪄 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: 65c0b52c-e3e3-4cf2-99f5-43585ac727e8

📥 Commits

Reviewing files that changed from the base of the PR and between 67bbe11 and 9c43b0a.

📒 Files selected for processing (6)
  • __tests__/components/brain/mobile/useBrainMobileActiveView.test.ts
  • __tests__/components/brain/my-stream/MyStreamWaveSubmissions.test.tsx
  • __tests__/hooks/useWaveDropsLeaderboard.test.ts
  • components/brain/mobile/useBrainMobileActiveView.ts
  • components/brain/my-stream/MyStreamWaveSubmissions.tsx
  • hooks/useWaveDropsLeaderboard.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/components/brain/my-stream/MyStreamWaveSubmissions.test.tsx
  • components/brain/mobile/useBrainMobileActiveView.ts

Comment thread components/brain/my-stream/MyStreamWaveSubmissions.tsx
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

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.

♻️ Duplicate comments (1)
components/brain/my-stream/MyStreamWaveSubmissions.tsx (1)

49-51: ⚠️ Potential issue | 🟡 Minor

Handle refetch/fetchNextPage promises explicitly.

At Line 50 and Line 89, promise rejections are still not explicitly handled (void refetch() / bare fetchNextPage()). This was previously flagged and can still fail static analysis or mask runtime failures.

Suggested fix
   const handleRetry = useCallback(() => {
-    void refetch();
+    refetch().catch(() => undefined);
   }, [refetch]);
@@
-      fetchNextPage();
+      fetchNextPage().catch(() => undefined);
#!/bin/bash
# Verify unresolved floating/suppressed promise patterns in this file
rg -nP '\bvoid\s+refetch\s*\(' components/brain/my-stream/MyStreamWaveSubmissions.tsx -C2
rg -nP '\bfetchNextPage\s*\(\s*\)\s*;' components/brain/my-stream/MyStreamWaveSubmissions.tsx -C2

# Inspect hook source to confirm these methods are promise-returning
fd -i useWaveDropsLeaderboard | xargs -I{} rg -n 'fetchNextPage|refetch|useInfiniteQuery' "{}" -C3

Also applies to: 89-90

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

In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx` around lines 49 - 51,
The refetch and fetchNextPage calls in handleRetry and the infinite-scroll
handler currently drop returned promises (void refetch() and bare
fetchNextPage()), so update those call sites to explicitly handle promise
rejections: make handleRetry an async callback and await refetch() inside a
try/catch (or call refetch().catch(err => /* log or set error state */)) and
likewise replace the bare fetchNextPage() with await fetchNextPage() inside a
try/catch or fetchNextPage().catch(err => /* handle/log */); refer to
handleRetry, refetch, and fetchNextPage to locate the places to change and
ensure any error is logged or surfaced to component state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@components/brain/my-stream/MyStreamWaveSubmissions.tsx`:
- Around line 49-51: The refetch and fetchNextPage calls in handleRetry and the
infinite-scroll handler currently drop returned promises (void refetch() and
bare fetchNextPage()), so update those call sites to explicitly handle promise
rejections: make handleRetry an async callback and await refetch() inside a
try/catch (or call refetch().catch(err => /* log or set error state */)) and
likewise replace the bare fetchNextPage() with await fetchNextPage() inside a
try/catch or fetchNextPage().catch(err => /* handle/log */); refer to
handleRetry, refetch, and fetchNextPage to locate the places to change and
ensure any error is logged or surfaced to component state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ee4d3e4-5427-40e1-810b-64492e896398

📥 Commits

Reviewing files that changed from the base of the PR and between 9c43b0a and 4b12861.

📒 Files selected for processing (3)
  • components/brain/my-stream/MyStreamWaveSubmissions.tsx
  • components/waves/leaderboard/WaveLeaderboardTime.tsx
  • knip.jsonc
✅ Files skipped from review due to trivial changes (1)
  • knip.jsonc
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/waves/leaderboard/WaveLeaderboardTime.tsx

@simo6529 simo6529 merged commit 5a73e63 into main Apr 14, 2026
8 checks passed
@simo6529 simo6529 deleted the persist-leaderboard-after-wave-is-over branch April 14, 2026 08:13
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