Skip to content

memes wave ui fixes#2252

Merged
ragnep merged 4 commits intomainfrom
memes-next-winner-info
Apr 11, 2026
Merged

memes wave ui fixes#2252
ragnep merged 4 commits intomainfrom
memes-next-winner-info

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Apr 10, 2026

Summary by CodeRabbit

  • New Features

    • Leaderboard header now shows a live time-left countdown plus next-decision date.
  • Style

    • Refined countdown typography and responsive layout for clearer display.
  • Refactor

    • Improved mobile vs. desktop tab scroller layout and create-curation action positioning.
    • Media-query handling updated for more reliable responsive behavior.
  • Bug Fixes / Behavior

    • Removed an internal countdown from a specific tab view; create-curation tooltip/interaction simplified.

Signed-off-by: ragnep <ragneinfo@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca3ba527-2e3e-4eab-b0b6-356873210561

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0ff27 and 831ec30.

📒 Files selected for processing (1)
  • hooks/useMediaQuery.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/useMediaQuery.ts

📝 Walkthrough

Walkthrough

Separated mobile and desktop tab scrollers, moved create-curation DOM wrapper, removed per-component class override, relocated countdown timing into WaveLeaderboardTime (feeding TimelineToggleHeader), adjusted countdown/time unit styles, simplified create-action and reworked useMediaQuery subscription logic.

Changes

Cohort / File(s) Summary
Mobile/Desktop Tab Layout Restructuring
components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
Split mobile and desktop tab scrollers (mobileTabsScrollerRef vs desktopTabsScrollerRef), render TabToggle separately per breakpoint, and moved MyStreamWaveCreateCurationAction into an sm:tw-ml-auto wrapper (removed its className usage).
Create Curation Action Simplification
components/brain/my-stream/tabs/MyStreamWaveCreateCurationAction.tsx
Removed className prop and clsx dependency; unified click handler; render tooltip only when not showing "create first curation" callout; simplified two distinct button variants.
Countdown Logic Relocation
components/brain/my-stream/tabs/MyStreamWaveTabsMeme.tsx, components/waves/leaderboard/WaveLeaderboardTime.tsx, components/waves/leaderboard/time/TimelineToggleHeader.tsx
Deleted countdown state/effects from MyStreamWaveTabsMeme; added interval-based timeLeft state/update in WaveLeaderboardTime; TimelineToggleHeader now accepts timeLeft and renders CompactTimeCountdown alongside formatted next-decision date.
Time Display Styling
components/waves/leaderboard/time/CompactTimeCountdown.tsx, components/waves/leaderboard/time/TimeUnitDisplay.tsx
Adjusted typography and layout classes (tw-text-xxstw-text-xs), removed optional label prop from CompactTimeCountdown, and simplified class lists for sizing/color consistency.
Media Query Hook Refactor
hooks/useMediaQuery.ts
Added getMediaQueryList(query) for safe matchMedia access (SSR-safe), switched to stable syncMatches via useEffectEvent, initialize from mediaQueryList.matches, and support both addEventListener('change') + legacy onchange fallback with proper cleanup.

Sequence Diagram(s)

sequenceDiagram
  participant Wave as WaveLeaderboardTime
  participant Header as TimelineToggleHeader
  participant Countdown as CompactTimeCountdown
  Note left of Wave: Interval (every 1s) recomputes timeLeft
  Wave->>Wave: calculateTimeLeft(nextDecisionTime)
  Wave-->>Header: props { nextDecisionTime, timeLeft }
  Header->>Countdown: render(timeLeft)
  Countdown-->>Header: formatted units
  Header-->>UI: combined countdown + formatted date shown
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • curation ui/ux fixes #2251: Touches MyStreamWaveCreateCurationAction and MyStreamWaveDesktopTabs — directly related to create-curation rendering/placement changes.
  • fix build #2234: Modifies WaveLeaderboardTime / TimelineToggleHeader — overlaps with timeLeft prop and countdown rendering.
  • Paused wave #2223: Edits TimelineToggleHeader timing/paused logic — potential interaction with new countdown wiring.

Suggested reviewers

  • simo6529
  • prxt6529

"I hop and count the seconds bright,
Mobile scrolls left, desktop scrolls right,
A little sync hums with each tick-tock,
Buttons shift, the tooltip finds its rock,
Hooray — the wave of changes takes flight!" 🐇✨

🚥 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 is vague and generic, using the non-descriptive term 'ui fixes' that doesn't convey meaningful information about the specific changes made in the changeset. Provide a more descriptive title that highlights the main change, such as 'Move time countdown logic from MyStreamWaveTabsMeme to WaveLeaderboardTime' or 'Refactor meme wave tab layout and time countdown display'.
✅ 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 memes-next-winner-info

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

@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 (4)
hooks/useMediaQuery.ts (1)

37-42: Consider memoizing the MediaQueryList reference.

The getSnapshot callback invokes getMediaQueryList(query) on every call, and useSyncExternalStore may call this synchronously multiple times per render. While browsers cache MediaQueryList objects for identical queries (so this works correctly), memoizing the reference would make the code more explicit and avoid repeated matchMedia calls.

♻️ Suggested optimization using useMemo
-import { useCallback, useSyncExternalStore } from "react";
+import { useCallback, useMemo, useSyncExternalStore } from "react";

 const getMediaQueryList = (query: string): MediaQueryList | null => {
   if (
     typeof window === "undefined" ||
     typeof window.matchMedia !== "function"
   ) {
     return null;
   }

   return window.matchMedia(query);
 };

 export function useMediaQuery(query: string): boolean {
+  const mediaQueryList = useMemo(() => getMediaQueryList(query), [query]);
+
   const subscribe = useCallback(
     (onStoreChange: () => void) => {
-      const mediaQueryList = getMediaQueryList(query);
       if (!mediaQueryList) {
         return () => {};
       }

       const handler = () => {
         onStoreChange();
       };

       if (typeof mediaQueryList.addEventListener === "function") {
         mediaQueryList.addEventListener("change", handler);
         return () => mediaQueryList.removeEventListener("change", handler);
       }

       mediaQueryList.addListener(handler);
       return () => mediaQueryList.removeListener(handler);
     },
-    [query]
+    [mediaQueryList]
   );

-  const getSnapshot = useCallback(
-    () => getMediaQueryList(query)?.matches ?? false,
-    [query]
-  );
+  const getSnapshot = useCallback(() => mediaQueryList?.matches ?? false, [mediaQueryList]);

   return useSyncExternalStore(subscribe, getSnapshot, () => false);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useMediaQuery.ts` around lines 37 - 42, The getSnapshot callback
currently calls getMediaQueryList(query) on every invocation; memoize the
MediaQueryList for the given query (e.g., via useMemo) and have getSnapshot read
.matches from that memoized reference instead, and ensure subscribe also uses
the same memoized MediaQueryList so match listeners attach/detach against the
stable object (references: getSnapshot, getMediaQueryList, subscribe, query,
useSyncExternalStore).
components/waves/leaderboard/time/TimelineToggleHeader.tsx (1)

65-78: Prefer reusing CompactTimeCountdown to avoid duplicated countdown markup.

Line 65 onward duplicates the same “Next winner” unit rendering already present in components/waves/leaderboard/time/CompactTimeCountdown.tsx. Reusing that component will reduce UI drift risk.

♻️ Proposed refactor
-import { TimeUnitDisplay } from "./TimeUnitDisplay";
+import { CompactTimeCountdown } from "./CompactTimeCountdown";
...
-          <div className="tw-hidden tw-items-center tw-gap-1.5 tw-whitespace-nowrap tw-text-xs tw-font-medium tw-text-iron-300 md:tw-inline-flex">
-            <span className="tw-whitespace-nowrap tw-font-medium tw-text-iron-300">
-              Next winner:
-            </span>
-            <div className="tw-flex tw-items-center tw-gap-x-1.5">
-              {timeLeft.days > 0 && (
-                <TimeUnitDisplay value={timeLeft.days} label="days" />
-              )}
-              <TimeUnitDisplay value={timeLeft.hours} label="hrs" />
-              <TimeUnitDisplay value={timeLeft.minutes} label="min" />
-              <TimeUnitDisplay value={timeLeft.seconds} label="sec" />
-            </div>
-          </div>
+          <CompactTimeCountdown timeLeft={timeLeft} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/waves/leaderboard/time/TimelineToggleHeader.tsx` around lines 65 -
78, The block in TimelineToggleHeader that manually renders the "Next winner"
countdown duplicates CompactTimeCountdown; remove the duplicated markup (the div
that contains the span "Next winner:" and the TimeUnitDisplay instances) and
instead import and use CompactTimeCountdown, passing the existing timeLeft (or
the values days/hours/minutes/seconds) and any label/props needed to render
"Next winner" (or wrap CompactTimeCountdown with the span if it needs the label
externally). Ensure you remove references to TimeUnitDisplay used only for this
block and update imports accordingly so TimelineToggleHeader uses
CompactTimeCountdown for the countdown UI.
components/brain/my-stream/MyStreamWaveDesktopTabs.tsx (2)

317-325: Deduplicate tab-selection logic to prevent drift.

The onSelect implementation is duplicated in mobile and desktop toggles. Extract one shared handler (e.g., handleTabSelect) and reuse it in both places.

♻️ Suggested refactor
+  const handleTabSelect = (key: string) => {
+    if (key.startsWith("curation:")) {
+      onSelectCuration(key.replace("curation:", ""));
+      return;
+    }
+
+    onSelectCuration(null);
+    setActiveTab(key as MyStreamWaveTab);
+  };
...
-            <TabToggle
-              options={mobileOptions}
-              activeKey={activeKey}
-              onSelect={(key) => {
-                if (key.startsWith("curation:")) {
-                  onSelectCuration(key.replace("curation:", ""));
-                  return;
-                }
-
-                onSelectCuration(null);
-                setActiveTab(key as MyStreamWaveTab);
-              }}
-            />
+            <TabToggle
+              options={mobileOptions}
+              activeKey={activeKey}
+              onSelect={handleTabSelect}
+            />
...
-        <TabToggle
-          options={options}
-          activeKey={activeKey}
-          onSelect={(key) => {
-            if (key.startsWith("curation:")) {
-              onSelectCuration(key.replace("curation:", ""));
-              return;
-            }
-
-            onSelectCuration(null);
-            setActiveTab(key as MyStreamWaveTab);
-          }}
-        />
+        <TabToggle
+          options={options}
+          activeKey={activeKey}
+          onSelect={handleTabSelect}
+        />

Also applies to: 346-354

🤖 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 317 -
325, Extract the duplicated onSelect logic into a shared handler (e.g.,
handleTabSelect) and use it for both desktop and mobile toggles: move the
current inline logic that checks key.startsWith("curation:"), calls
onSelectCuration(...) or onSelectCuration(null), and calls setActiveTab(key as
MyStreamWaveTab) into a single function named handleTabSelect, then replace the
inline onSelect callbacks with handleTabSelect in both places; ensure
handleTabSelect accepts the same key parameter and preserves the behavior for
curation keys and non-curation keys.

308-356: Mount only one tab tree per breakpoint to avoid doubled UI work.

Both mobile and desktop TabToggle branches are rendered simultaneously and one is only hidden via CSS. Consider conditionally rendering a single branch (based on breakpoint) to reduce duplicate reconciliation and DOM size.

🤖 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 308 -
356, Both mobile and desktop TabToggle trees are mounted together and only
hidden via CSS; change to render only one based on breakpoint (e.g., use a media
hook like useMediaQuery/useIsMobile or a passed prop) so you don't mount both.
Replace the unconditional rendering of the mobile div (ref
mobileTabsScrollerRef, mobileOptions, mobileOverflowItems, EllipsisVerticalIcon,
CompactMenu, onSelect logic) and the desktop div (ref desktopTabsScrollerRef,
options) with a single conditional: if isMobile render the mobile tree and its
ref/menu/options, else render the desktop tree and its ref/options; keep the
existing onSelect handlers (calling onSelectCuration and setActiveTab) unchanged
and ensure refs are only attached to the rendered branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/brain/my-stream/MyStreamWaveDesktopTabs.tsx`:
- Around line 317-325: Extract the duplicated onSelect logic into a shared
handler (e.g., handleTabSelect) and use it for both desktop and mobile toggles:
move the current inline logic that checks key.startsWith("curation:"), calls
onSelectCuration(...) or onSelectCuration(null), and calls setActiveTab(key as
MyStreamWaveTab) into a single function named handleTabSelect, then replace the
inline onSelect callbacks with handleTabSelect in both places; ensure
handleTabSelect accepts the same key parameter and preserves the behavior for
curation keys and non-curation keys.
- Around line 308-356: Both mobile and desktop TabToggle trees are mounted
together and only hidden via CSS; change to render only one based on breakpoint
(e.g., use a media hook like useMediaQuery/useIsMobile or a passed prop) so you
don't mount both. Replace the unconditional rendering of the mobile div (ref
mobileTabsScrollerRef, mobileOptions, mobileOverflowItems, EllipsisVerticalIcon,
CompactMenu, onSelect logic) and the desktop div (ref desktopTabsScrollerRef,
options) with a single conditional: if isMobile render the mobile tree and its
ref/menu/options, else render the desktop tree and its ref/options; keep the
existing onSelect handlers (calling onSelectCuration and setActiveTab) unchanged
and ensure refs are only attached to the rendered branch.

In `@components/waves/leaderboard/time/TimelineToggleHeader.tsx`:
- Around line 65-78: The block in TimelineToggleHeader that manually renders the
"Next winner" countdown duplicates CompactTimeCountdown; remove the duplicated
markup (the div that contains the span "Next winner:" and the TimeUnitDisplay
instances) and instead import and use CompactTimeCountdown, passing the existing
timeLeft (or the values days/hours/minutes/seconds) and any label/props needed
to render "Next winner" (or wrap CompactTimeCountdown with the span if it needs
the label externally). Ensure you remove references to TimeUnitDisplay used only
for this block and update imports accordingly so TimelineToggleHeader uses
CompactTimeCountdown for the countdown UI.

In `@hooks/useMediaQuery.ts`:
- Around line 37-42: The getSnapshot callback currently calls
getMediaQueryList(query) on every invocation; memoize the MediaQueryList for the
given query (e.g., via useMemo) and have getSnapshot read .matches from that
memoized reference instead, and ensure subscribe also uses the same memoized
MediaQueryList so match listeners attach/detach against the stable object
(references: getSnapshot, getMediaQueryList, subscribe, query,
useSyncExternalStore).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2b5b6f88-c634-4162-94cf-a73653f345aa

📥 Commits

Reviewing files that changed from the base of the PR and between 000c8e6 and 7c73063.

📒 Files selected for processing (8)
  • components/brain/my-stream/MyStreamWaveDesktopTabs.tsx
  • components/brain/my-stream/tabs/MyStreamWaveCreateCurationAction.tsx
  • components/brain/my-stream/tabs/MyStreamWaveTabsMeme.tsx
  • components/waves/leaderboard/WaveLeaderboardTime.tsx
  • components/waves/leaderboard/time/CompactTimeCountdown.tsx
  • components/waves/leaderboard/time/TimeUnitDisplay.tsx
  • components/waves/leaderboard/time/TimelineToggleHeader.tsx
  • hooks/useMediaQuery.ts

ragnep added 3 commits April 10, 2026 21:50
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

@ragnep ragnep merged commit 1794dcd into main Apr 11, 2026
8 checks passed
@ragnep ragnep deleted the memes-next-winner-info branch April 11, 2026 07:18
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