Skip to content

Discover page wave card design#1568

Merged
ragnep merged 10 commits intomainfrom
ui-ux-fixes
Oct 28, 2025
Merged

Discover page wave card design#1568
ragnep merged 10 commits intomainfrom
ui-ux-fixes

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Oct 27, 2025

Summary by CodeRabbit

  • New Features

    • Wave cards are fully clickable with ARIA labels and keyboard navigation (Enter/Space).
  • Improvements

    • Richer avatar handling (all contributors), updated card layout, responsive height and grid breakpoint tweaks, visual spacing and padding adjustments, and scrollbar behavior change.
    • Interactive elements (follow button, avatar links) intentionally do not trigger card navigation; button label updated to "Create DM".
  • Tests

    • Expanded tests for navigation, keyboard interaction, and contributor avatar rendering.
  • Documentation

    • Added/updated tickets and state docs describing plans and test stabilization.

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

coderabbitai Bot commented Oct 27, 2025

Walkthrough

Makes Wave cards fully clickable and keyboard-accessible with router.push navigation, refactors contributor avatar rendering to show all contributors, updates related tests and tickets, adjusts small-screen max-heights in two drop components, and moves an html scrollbar-gutter rule into a media query.

Changes

Cohort / File(s) Summary
Wave Card Interactivity
components/waves/list/WaveItem.tsx
Wrap card in CardContainer, compute waveHref via getWaveRoute, handle click + Enter/Space to router.push, guard navigation when events originate from interactive descendants, add ARIA label, image lazy-loading, gradient/banner/level class consolidation.
Avatar & Contributor Rendering
components/waves/list/WaveItemDropped.tsx
Render all contributors (map), per-item unique keys (include index), compute per-contributor href, wrap avatar in Link when available, set loading="lazy"/decoding="async", update layout/metrics and truncation.
Follow Button Styling
components/waves/list/WaveItemFollow.tsx
Adjust follow/unfollow button padding/size Tailwind classes (visual-only).
Headers & List Layout
components/waves/list/header/WavesListHeader.tsx, components/waves/list/WavesListWrapper.tsx
Remove Tailwind typography from title (plain <h1>); change grid breakpoint xl:tw-grid-cols-3lg:tw-grid-cols-3.
Drops Max-Height
components/waves/drops/ArtistActiveSubmissionContent.tsx, components/waves/drops/ArtistWinningArtworksContent.tsx
Increase small-screen max-height from calc(80vh-120px)calc(90vh-140px).
Global Styles
styles/globals.scss
Reformat gradients/utility blocks and move html { scrollbar-gutter } into @media (min-width:1300px) (functional change); other formatting tweaks.
Messaging & Button Spacing
components/brain/left-sidebar/web/WebDirectMessagesList.tsx, components/brain/left-sidebar/web/WebUnifiedWavesListWaves.tsx, components/messages/MessagesView.tsx, components/user/waves/UserPageWavesSearch.tsx
Standardize button padding to tw-p-2.5, adjust icon right-margins tw-mr-1.5tw-mr-2, rename internal flag to showCreateWaveButton, change label "New Direct Message" → "Create DM".
Tests & Tickets
__tests__/components/waves/list/WaveItem.test.tsx, __tests__/components/waves/list/WaveItemDropped.test.tsx, __tests__/components/waves/Waves.test.tsx, codex/STATE.md, codex/tickets/TKT-0016.md, codex/tickets/TKT-0017.md, codex/tickets/TKT-0011.md, codex/tickets/TKT-0018.md
Expand WaveItem tests with richer fixtures, fireEvent, and mocked useRouter.push; assert card/keyboard navigation and that Follow prevents navigation; update dropped-avatar test to expect all contributors; add ticket docs and STATE entries.
Minor formatting / markup
assorted files (e.g., styles/globals.scss)
Misc. formatting, spacing, and small class tweaks with no additional behavioral changes beyond items above.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Card as WaveItem (CardContainer)
    participant Child as Interactive Descendant
    participant Router as Next.js Router

    User->>Card: Click card area
    Card->>Card: compute waveHref & check target via INTERACTIVE_CHILD_SELECTOR
    alt waveHref exists & not interactive child
        Card->>Router: push(waveHref)
    else event inside interactive child
        Card-->>Child: no navigation (child handles event)
    end

    User->>Card: Press Enter / Space
    alt Card focused & waveHref exists
        Card->>Router: push(waveHref)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • components/waves/list/WaveItem.tsx — navigation guards, keyboard handling, ARIA semantics, INTERACTIVE_CHILD_SELECTOR usage.
    • components/waves/list/WaveItemDropped.tsx — contributor mapping, key composition, Link wrapping and responsive layout.
    • Tests — __tests__/components/waves/list/WaveItem.test.tsx (mocked useRouter.push, event simulations, assertions).
    • styles/globals.scss — visual/regression impact from moving scrollbar-gutter into a media query.

Possibly related PRs

Suggested reviewers

  • simo6529

Poem

🐰 I hopped upon a clickable card,

I nudged the keys and leapt so hard.
Avatars parade in tidy rows,
One gentle click — the router goes.
Small paws, new paths, and brighter flows. ✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Discover page wave card design" is clearly related to the changeset. The PR contains substantial changes to wave card components (WaveItem.tsx, WaveItemDropped.tsx, WaveItemFollow.tsx) and their styling, layout, and responsive behavior across the discover page. The title accurately reflects that the work centers on updating the wave card design and user experience. While the changes also include functional improvements like making cards fully clickable and various related UI refinements, the title captures the primary design-focused work without being misleading or off-topic. The title is specific enough that a teammate scanning history would understand the focus is on discover page wave card design.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ui-ux-fixes

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.

Actionable comments posted: 8

🧹 Nitpick comments (3)
components/waves/list/WaveItem.tsx (3)

103-114: Keydown handler: prefer event.code for Space (and include legacy "Spacebar")

For role="link", Enter is standard; supporting Space is fine for cards, but check via event.code === "Space" or include "Spacebar" to cover older UAs. Current "Space" isn’t a standard key value.

-      if (event.key === "Enter" || event.key === " " || event.key === "Space") {
+      if (
+        event.key === "Enter" ||
+        event.key === " " ||
+        event.code === "Space" ||
+        event.key === "Spacebar"
+      ) {

85-101: Broaden interactive-child guard to avoid accidental navigation

Include elements with role="button" in the closest() selector so clicks on custom buttons don’t trigger card navigation.

-        target?.closest(
-          "a, button, input, textarea, select, [data-wave-item-interactive='true']"
-        )
+        target?.closest(
+          "a, button, [role='button'], input, textarea, select, [data-wave-item-interactive='true']"
+        )

138-145: Optional: consider next/image for better image optimization

Using next/image would improve lazy loading, responsive sizing, and priority hints. Not required for this PR, but worth tracking.

Also applies to: 177-193

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c01bf38 and 641bfed.

📒 Files selected for processing (9)
  • __tests__/components/waves/list/WaveItem.test.tsx (3 hunks)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0011.md (1 hunks)
  • components/waves/drops/ArtistActiveSubmissionContent.tsx (1 hunks)
  • components/waves/drops/ArtistWinningArtworksContent.tsx (1 hunks)
  • components/waves/list/WaveItem.tsx (2 hunks)
  • components/waves/list/WaveItemDropped.tsx (2 hunks)
  • components/waves/list/header/WavesListHeader.tsx (1 hunks)
  • styles/globals.scss (13 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/list/header/WavesListHeader.tsx
  • components/waves/list/WaveItemDropped.tsx
  • components/waves/drops/ArtistActiveSubmissionContent.tsx
  • components/waves/drops/ArtistWinningArtworksContent.tsx
  • components/waves/list/WaveItem.tsx
  • __tests__/components/waves/list/WaveItem.test.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/waves/list/header/WavesListHeader.tsx
  • components/waves/list/WaveItemDropped.tsx
  • components/waves/drops/ArtistActiveSubmissionContent.tsx
  • components/waves/drops/ArtistWinningArtworksContent.tsx
  • components/waves/list/WaveItem.tsx
  • __tests__/components/waves/list/WaveItem.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/waves/list/WaveItem.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/waves/list/WaveItem.test.tsx
**/__tests__/**

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in __tests__ directories when organizing test suites

Files:

  • __tests__/components/waves/list/WaveItem.test.tsx
**/*.test.tsx

📄 CodeRabbit inference engine (AGENTS.md)

When colocating tests with components, name them ComponentName.test.tsx

Files:

  • __tests__/components/waves/list/WaveItem.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/waves/list/WaveItem.test.tsx
🧬 Code graph analysis (3)
components/waves/list/WaveItemDropped.tsx (1)
helpers/Helpers.ts (1)
  • numberWithCommas (83-100)
components/waves/list/WaveItem.tsx (5)
helpers/Helpers.ts (2)
  • getRandomColorWithSeed (736-750)
  • numberWithCommas (83-100)
helpers/navigation.helpers.ts (1)
  • getWaveRoute (23-71)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/list/WaveItemDropped.tsx (1)
  • WaveItemDropped (6-50)
components/waves/list/WaveItemFollow.tsx (1)
  • WaveItemFollow (22-186)
__tests__/components/waves/list/WaveItem.test.tsx (1)
components/waves/list/WaveItem.tsx (1)
  • WaveItem (29-284)
🪛 markdownlint-cli2 (0.18.1)
codex/tickets/TKT-0011.md

22-22: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


23-23: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


24-24: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
components/waves/list/header/WavesListHeader.tsx (1)

41-41: Verify removal of Tailwind styling is intentional.

The h1 element now uses global styles from globals.scss instead of inline Tailwind classes. This deviates from the coding guideline to "Use TailwindCSS for styling" for .tsx files. Ensure this change aligns with the desired styling approach.

codex/STATE.md (1)

17-17: LGTM!

The new TKT-0011 entry follows the table format and includes all required fields.

styles/globals.scss (2)

190-194: Verify scrollbar-gutter media query change is intentional.

The scrollbar-gutter: stable both-edges declaration was moved from applying globally to only screens with min-width: 1300px. This changes layout behavior on smaller screens where the scrollbar gutter will no longer be reserved, potentially causing horizontal layout shifts when scrollbars appear/disappear.


22-23: LGTM on formatting changes.

The reformatting of calc() expressions, gradients, and comments improves consistency without affecting behavior.

Also applies to: 82-93, 239-241, 559-560, 618-651, 661-670

__tests__/components/waves/list/WaveItem.test.tsx (1)

1-82: LGTM!

The test updates comprehensively cover the new interactive card behavior:

  • Navigation via card click
  • Keyboard accessibility (Enter and Space keys)
  • Guarded navigation that respects nested interactive elements

The mocks are appropriate and align with coding guidelines for testing.

components/waves/list/WaveItemDropped.tsx (1)

25-26: LGTM on performance attributes.

Adding loading="lazy" and decoding="async" improves performance by deferring image loading and preventing blocking during decode.

components/waves/list/WaveItem.tsx (2)

78-101: Interactive-card navigation and keyboard support look solid

Good separation of concerns with navigateToWave + guarded click/keydown handlers; prevents nested control conflicts and ensures focusability.

Please verify tests cover:

  • Click on whitespace of card navigates.
  • Click on WaveItemFollow/links does not navigate.
  • Keyboard Enter/Space on card navigates; on inner controls does not.

Also applies to: 103-114


29-37: Props marked readonly — nice adherence to typing guideline

The props interface uses readonly consistently. No action needed.

Comment thread codex/tickets/TKT-0011.md Outdated
Comment thread components/waves/drops/ArtistActiveSubmissionContent.tsx
Comment thread components/waves/drops/ArtistWinningArtworksContent.tsx
Comment thread components/waves/list/WaveItem.tsx Outdated
Comment thread components/waves/list/WaveItem.tsx
Comment thread components/waves/list/WaveItem.tsx Outdated
Comment thread components/waves/list/WaveItemDropped.tsx Outdated
Comment thread components/waves/list/WaveItemDropped.tsx Outdated
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Signed-off-by: ragnep <ragneinfo@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/user/waves/UserPageWavesSearch.tsx (1)

86-86: Inconsistent icon spacing between adjacent buttons.

Line 111 updates the Create DM icon margin to tw-mr-2, but Line 86's Create Wave icon still uses tw-mr-1.5. This creates visual inconsistency between two similar, adjacent buttons.

Apply this diff to align the Create Wave icon margin:

-            className="tw-size-5 tw-mr-1.5 -tw-ml-1 tw-flex-shrink-0"
+            className="tw-size-5 tw-mr-2 -tw-ml-1 tw-flex-shrink-0"

Also applies to: 111-111

♻️ Duplicate comments (2)
components/waves/list/WaveItem.tsx (2)

3-7: Replace Heroicons with FontAwesome (coding guideline violation).

This file violates the project guideline requiring FontAwesome for icons in **/*.tsx files. The Heroicons imports should be replaced with FontAwesome equivalents.

As per coding guidelines

Apply this diff:

-import {
-  ChatBubbleLeftRightIcon,
-  UsersIcon,
-} from "@heroicons/react/24/outline";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faComments, faUsers } from "@fortawesome/free-solid-svg-icons";

Then update the icon usages at lines 225–230 and 238–246:

-            <ChatBubbleLeftRightIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faComments}
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+              aria-hidden="true"
+            />
-            <UsersIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faUsers}
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+              aria-hidden="true"
+            />

38-68: Guard author handle to avoid linking to "/undefined".

At line 179, the code builds a link using wave.author.handle without checking if the handle exists. If wave.author.handle is undefined, the href becomes "/undefined".

Compute a safe authorHref and conditionally render the Link:

 const author = wave?.author;
+const authorHref = author?.handle ? `/${author.handle}` : undefined;

Then at lines 178–211, wrap the Link in a conditional:

-          <Link
-            href={`/${wave.author.handle}`}
+          {authorHref ? (
+            <Link
+              href={authorHref}
               prefetch={false}
               className="tw-mt-1 tw-group tw-no-underline tw-flex tw-flex-wrap tw-items-center tw-gap-x-2 tw-gap-y-1"
             >
               {/* ... avatar and name ... */}
             </Link>
+          ) : (
+            <span className="tw-mt-1 tw-flex tw-flex-wrap tw-items-center tw-gap-x-2 tw-gap-y-1">
+              {/* ... same avatar and name content ... */}
+            </span>
+          )}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b76141 and 5e2f758.

📒 Files selected for processing (15)
  • __tests__/components/waves/Waves.test.tsx (2 hunks)
  • __tests__/components/waves/list/WaveItemDropped.test.tsx (1 hunks)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0011.md (1 hunks)
  • codex/tickets/TKT-0016.md (1 hunks)
  • codex/tickets/TKT-0017.md (1 hunks)
  • components/brain/left-sidebar/web/WebDirectMessagesList.tsx (1 hunks)
  • components/brain/left-sidebar/web/WebUnifiedWavesListWaves.tsx (5 hunks)
  • components/messages/MessagesView.tsx (1 hunks)
  • components/user/waves/UserPageWavesSearch.tsx (1 hunks)
  • components/waves/list/WaveItem.tsx (2 hunks)
  • components/waves/list/WaveItemDropped.tsx (1 hunks)
  • components/waves/list/WaveItemFollow.tsx (1 hunks)
  • components/waves/list/WavesListWrapper.tsx (1 hunks)
  • components/waves/list/header/WavesListHeader.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/STATE.md
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/brain/left-sidebar/web/WebDirectMessagesList.tsx
  • components/waves/list/WavesListWrapper.tsx
  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • components/waves/list/WaveItemFollow.tsx
  • components/waves/list/WaveItemDropped.tsx
  • components/brain/left-sidebar/web/WebUnifiedWavesListWaves.tsx
  • __tests__/components/waves/Waves.test.tsx
  • components/messages/MessagesView.tsx
  • components/user/waves/UserPageWavesSearch.tsx
  • components/waves/list/WaveItem.tsx
  • components/waves/list/header/WavesListHeader.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/brain/left-sidebar/web/WebDirectMessagesList.tsx
  • components/waves/list/WavesListWrapper.tsx
  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • components/waves/list/WaveItemFollow.tsx
  • components/waves/list/WaveItemDropped.tsx
  • components/brain/left-sidebar/web/WebUnifiedWavesListWaves.tsx
  • __tests__/components/waves/Waves.test.tsx
  • components/messages/MessagesView.tsx
  • components/user/waves/UserPageWavesSearch.tsx
  • components/waves/list/WaveItem.tsx
  • components/waves/list/header/WavesListHeader.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • __tests__/components/waves/Waves.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • __tests__/components/waves/Waves.test.tsx
**/__tests__/**

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in __tests__ directories when organizing test suites

Files:

  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • __tests__/components/waves/Waves.test.tsx
**/*.test.tsx

📄 CodeRabbit inference engine (AGENTS.md)

When colocating tests with components, name them ComponentName.test.tsx

Files:

  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • __tests__/components/waves/Waves.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/waves/list/WaveItemDropped.test.tsx
  • __tests__/components/waves/Waves.test.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-23T06:36:34.125Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: AGENTS.md:0-0
Timestamp: 2025-10-23T06:36:34.125Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/waves/Waves.test.tsx
📚 Learning: 2025-09-28T12:33:07.561Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: __mocks__/AGENTS.md:0-0
Timestamp: 2025-09-28T12:33:07.561Z
Learning: Applies to __mocks__/**/__mocks__/**/*.{js,jsx,ts,tsx} : Keep mock implementations minimal—only what’s necessary for the test scenarios

Applied to files:

  • __tests__/components/waves/Waves.test.tsx
🧬 Code graph analysis (4)
__tests__/components/waves/list/WaveItemDropped.test.tsx (1)
components/waves/list/WaveItemDropped.tsx (1)
  • WaveItemDropped (6-60)
components/waves/list/WaveItemDropped.tsx (2)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
helpers/Helpers.ts (1)
  • numberWithCommas (83-100)
__tests__/components/waves/Waves.test.tsx (1)
hooks/useDeviceInfo.ts (1)
  • useDeviceInfo (22-78)
components/waves/list/WaveItem.tsx (5)
helpers/Helpers.ts (2)
  • getRandomColorWithSeed (736-750)
  • numberWithCommas (83-100)
helpers/navigation.helpers.ts (1)
  • getWaveRoute (23-71)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/list/WaveItemDropped.tsx (1)
  • WaveItemDropped (6-60)
components/waves/list/WaveItemFollow.tsx (1)
  • WaveItemFollow (22-186)
🪛 LanguageTool
codex/tickets/TKT-0017.md

[grammar] ~34-~34: Ensure spelling is correct
Context: ...- 2025-10-27T14:10:00Z – Documented the flakey Waves tests and logged the need for dev...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (21)
components/messages/MessagesView.tsx (1)

54-54: LGTM! Consistent label abbreviation.

The button label change from "New Direct Message" to "Create DM" improves brevity and aligns with similar changes elsewhere in the PR (e.g., WavesListHeader.tsx).

codex/tickets/TKT-0011.md (1)

48-48: LGTM! Standard file formatting.

The trailing blank line is a standard practice for text files and has no impact on the documentation content.

components/waves/list/WaveItemFollow.tsx (1)

149-149: Verify touch target size remains adequate.

The padding reduction from tw-px-3 tw-py-2 to tw-px-2.5 tw-py-1.5 decreases both dimensions. Ensure the final button size (including the gradient wrapper padding) meets the minimum 44×44px touch target size for mobile accessibility.

components/brain/left-sidebar/web/WebDirectMessagesList.tsx (1)

182-182: LGTM! Consistent padding standardization.

The padding update to "tw-p-2.5" aligns with similar changes in WebUnifiedWavesListWaves.tsx and provides a slightly larger, more accessible hit area.

components/waves/list/WavesListWrapper.tsx (1)

217-217: Verify card appearance at the lg breakpoint.

The change from xl:tw-grid-cols-3 to lg:tw-grid-cols-3 activates the three-column layout at 1024px instead of 1280px. Please verify that Wave cards render properly at the narrower column width, especially regarding text truncation and image sizing.

components/brain/left-sidebar/web/WebUnifiedWavesListWaves.tsx (2)

97-97: LGTM! Improved variable naming.

The rename from shouldRenderCreateWaveButton to showCreateWaveButton is more concise while maintaining clarity. All references are consistently updated.

Also applies to: 132-132, 156-156


142-142: LGTM! Consistent padding standardization.

The padding updates to "tw-p-2.5" match the standardization applied in WebDirectMessagesList.tsx, improving consistency and accessibility across button components.

Also applies to: 165-165

components/waves/list/header/WavesListHeader.tsx (2)

92-92: Note: Icon spacing differs between buttons.

The Create DM button icon uses tw-mr-2, while the Create Wave button icon (line 69) uses tw-mr-1.5. This may be intentional to account for the different icon sizes (tw-size-4 vs. tw-size-5), but verify that the visual spacing appears balanced.


41-41: Global h1 styles are properly defined and provide appropriate styling.

The global stylesheet at styles/globals.scss includes h1 style definitions (line 285–289) with font-size: 20px, font-weight: 700, and color from variables, plus responsive styling for mobile screens. The code change is safe.

__tests__/components/waves/Waves.test.tsx (1)

41-41: LGTM! Improved test mock management.

The centralized deviceInfoMock reference improves test maintainability and type safety. The default mock state in beforeEach and per-test overrides provide better control over test isolation.

Minor note: The cleanup at line 131 is redundant since beforeEach already resets the mock, but it documents intent and has no harm.

Also applies to: 49-49, 87-87, 93-93, 131-131

__tests__/components/waves/list/WaveItemDropped.test.tsx (1)

21-29: LGTM!

The test correctly validates that all contributor avatars are rendered, aligning with the updated production component behavior.

codex/tickets/TKT-0016.md (1)

1-53: LGTM!

The ticket documentation is well-structured and provides clear context, plan, and acceptance criteria for the Wave card interactivity feature.

components/waves/list/WaveItemDropped.tsx (3)

7-7: LGTM!

Safe extraction of contributors with appropriate fallback to an empty array.


12-48: LGTM!

The per-contributor avatar rendering correctly addresses previous review concerns:

  • Unique keys now include identity, pfp, and index to prevent duplicates.
  • Alt text gracefully handles missing contributor identity.
  • Lazy loading and async decoding optimize performance.
  • Conditional Link wrapping with prefetch={false} is appropriate.

50-57: LGTM!

The drops count layout correctly handles pluralization and uses appropriate responsive styling.

components/waves/list/WaveItem.tsx (6)

18-27: LGTM!

The LEVEL_CLASSES constant is well-structured with proper readonly typing for level-based color mapping.


78-114: LGTM!

The navigation event handlers correctly:

  • Guard against missing waveHref.
  • Prevent navigation when clicking interactive descendants.
  • Handle keyboard accessibility (Enter and Space keys).

117-128: LGTM!

The interactive card wrapper correctly:

  • Applies conditional styling based on isInteractive.
  • Sets appropriate ARIA attributes (role, aria-label).
  • Manages focus and keyboard navigation with tabIndex.
  • Includes proper spacing in className interpolation.

129-173: LGTM!

The hero image section correctly:

  • Applies gradient backgrounds with fallback colors.
  • Lazy-loads images with appropriate scaling and async decoding.
  • Conditionally renders the wave name as a Link when navigable.
  • Provides proper ARIA labeling.

249-262: LGTM!

The tooltip implementation for the followers count is properly configured with appropriate styling and positioning strategy.


265-281: LGTM!

The bottom row correctly:

  • Renders WaveItemDropped as a non-interactive subcomponent.
  • Marks WaveItemFollow with data-wave-item-interactive="true" to prevent card-level navigation when interacting with the follow button.

Comment thread codex/tickets/TKT-0017.md
Signed-off-by: ragnep <ragneinfo@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
components/waves/list/WaveItem.tsx (2)

10-13: Replace Heroicons with FontAwesome (repo guideline).

Comply with TSX icon standard; swap Heroicons for FontAwesome.

-import {
-  ChatBubbleLeftRightIcon,
-  UsersIcon,
-} from "@heroicons/react/24/outline";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faComments, faUsers } from "@fortawesome/free-solid-svg-icons";
-            <ChatBubbleLeftRightIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faComments}
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+              aria-hidden="true"
+            />
-            <UsersIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faUsers}
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+              aria-hidden="true"
+            />

As per coding guidelines

Also applies to: 282-286, 295-298


120-123: Guard author handle to avoid linking to “/undefined”; render non-link fallback.

Build authorHref safely and conditionally render Link/span.

   const author = wave?.author;
+  const authorHref = author?.handle ? `/${author.handle}` : undefined;
   const router = useRouter();
   const tooltipBaseId = useId();
-        {wave ? (
-          <Link
-            href={`/${wave.author.handle}`}
-            prefetch={false}
-            className="tw-mt-1 tw-group/author tw-no-underline tw-flex tw-flex-wrap tw-items-center tw-gap-x-2 tw-gap-y-1"
-          >
+        {wave ? (
+          authorHref ? (
+            <Link
+              href={authorHref}
+              prefetch={false}
+              className="tw-mt-1 tw-group/author tw-no-underline tw-flex tw-flex-wrap tw-items-center tw-gap-x-2 tw-gap-y-1"
+            >
               <div className="tw-h-6 tw-w-6 tw-flex-shrink-0">
                 {wave?.author.pfp ? (
                   <img
                     className="tw-h-full tw-w-full tw-rounded-md tw-object-cover tw-bg-iron-800 tw-ring-1 tw-ring-white/10 desktop-hover:group-hover/author:tw-ring-white/30 desktop-hover:group-hover/author:tw-ring-offset-1 desktop-hover:group-hover/author:tw-ring-offset-iron-950 tw-transition tw-duration-300 tw-ease-out"
                     src={getScaledImageUri(
                       wave.author.pfp,
                       ImageScale.W_AUTO_H_50
                     )}
                     alt={
                       wave?.author.handle
                         ? `${wave.author.handle} avatar`
                         : "Author avatar"
                     }
                     loading="lazy"
                     decoding="async"
                   />
                 ) : (
                   <div className="tw-h-full tw-w-full tw-rounded-md tw-bg-iron-800 tw-ring-1 tw-ring-white/10" />
                 )}
               </div>
               <span className="tw-text-sm tw-font-semibold tw-text-white desktop-hover:group-hover/author:tw-text-iron-400 tw-transition tw-duration-300 tw-ease-out">
                 {wave?.author.handle ?? userPlaceholder}
               </span>
               <div
                 className={`${resolveLevelClasses(
                   wave?.author.level
                 )} tw-border-none tw-inline-flex tw-items-center tw-rounded-xl tw-bg-transparent tw-px-2 tw-py-1 tw-font-semibold tw-ring-2 tw-ring-inset tw-text-[0.625rem] tw-leading-3`}
               >
                 Level {wave.author.level}
               </div>
-          </Link>
+            </Link>
+          ) : (
+            <span className="tw-mt-1 tw-group/author tw-flex tw-flex-wrap tw-items-center tw-gap-x-2 tw-gap-y-1">
+              <div className="tw-h-6 tw-w-6 tw-flex-shrink-0">
+                {wave?.author.pfp ? (
+                  <img
+                    className="tw-h-full tw-w-full tw-rounded-md tw-object-cover tw-bg-iron-800 tw-ring-1 tw-ring-white/10"
+                    src={getScaledImageUri(wave.author.pfp, ImageScale.W_AUTO_H_50)}
+                    alt="Author avatar"
+                    loading="lazy"
+                    decoding="async"
+                  />
+                ) : (
+                  <div className="tw-h-full tw-w-full tw-rounded-md tw-bg-iron-800 tw-ring-1 tw-ring-white/10" />
+                )}
+              </div>
+              <span className="tw-text-sm tw-font-semibold tw-text-white">
+                {wave?.author.handle ?? userPlaceholder}
+              </span>
+              <div
+                className={`${resolveLevelClasses(
+                  wave?.author.level
+                )} tw-border-none tw-inline-flex tw-items-center tw-rounded-xl tw-bg-transparent tw-px-2 tw-py-1 tw-font-semibold tw-ring-2 tw-ring-inset tw-text-[0.625rem] tw-leading-3`}
+              >
+                Level {wave.author.level}
+              </div>
+            </span>
+          )
         ) : (

Also applies to: 231-277

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2f758 and ce82811.

📒 Files selected for processing (2)
  • components/waves/list/WaveItem.tsx (2 hunks)
  • components/waves/list/WaveItemDropped.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/list/WaveItem.tsx
  • components/waves/list/WaveItemDropped.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/waves/list/WaveItem.tsx
  • components/waves/list/WaveItemDropped.tsx
🧬 Code graph analysis (2)
components/waves/list/WaveItem.tsx (5)
helpers/Helpers.ts (2)
  • getRandomColorWithSeed (736-750)
  • numberWithCommas (83-100)
helpers/navigation.helpers.ts (1)
  • getWaveRoute (23-71)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/list/WaveItemDropped.tsx (1)
  • WaveItemDropped (6-60)
components/waves/list/WaveItemFollow.tsx (1)
  • WaveItemFollow (22-186)
components/waves/list/WaveItemDropped.tsx (2)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
helpers/Helpers.ts (1)
  • numberWithCommas (83-100)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread components/waves/list/WaveItem.tsx
Comment thread components/waves/list/WaveItem.tsx
Comment thread components/waves/list/WaveItemDropped.tsx
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

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

♻️ Duplicate comments (2)
components/waves/list/WaveItem.tsx (2)

11-13: Replace Heroicons with FontAwesome per coding guidelines.

The imports still use @heroicons/react/24/outline instead of FontAwesome, violating the project guideline for **/*.tsx files.

Apply this diff:

-import {
-  ChatBubbleLeftRightIcon,
-  UsersIcon,
-} from "@heroicons/react/24/outline";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faComments, faUsers } from "@fortawesome/free-solid-svg-icons";

Then update the usages at lines 306-310 and 319-323:

-            <ChatBubbleLeftRightIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faComments}
+              aria-hidden="true"
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+            />
-            <UsersIcon
-              aria-hidden="true"
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
-            />
+            <FontAwesomeIcon
+              icon={faUsers}
+              aria-hidden="true"
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-iron-400"
+            />

As per coding guidelines


65-79: Nested anchor elements create invalid HTML.

The CardContainer returns a <Link> (anchor) when interactive, but the card content includes nested <Link> elements for the title (lines 281-287) and author (lines 191-202). This produces invalid HTML structure even though JavaScript prevents the default navigation behavior.

Refactor CardContainer to use a <div> with role="link" and keyboard/click handlers:

 function CardContainer({
   isInteractive,
   href,
   ariaLabel,
   onClick,
   onKeyDown,
   children,
 }: CardContainerProps) {
   const className = `${CARD_BASE_CLASSES} ${
     isInteractive ? CARD_INTERACTIVE_CLASSES : ""
   }`;

-  if (isInteractive && href) {
-    return (
-      <Link
-        href={href}
-        prefetch={false}
-        className={className}
-        aria-label={ariaLabel}
-        style={{ textDecoration: "none" }}
-        onClick={onClick}
-        onKeyDown={onKeyDown}
-      >
-        {children}
-      </Link>
-    );
-  }
-
-  return (
-    <div className={className} aria-label={ariaLabel}>
-      {children}
-    </div>
-  );
+  return (
+    <div
+      className={className}
+      aria-label={ariaLabel}
+      role={isInteractive && href ? "link" : undefined}
+      tabIndex={isInteractive && href ? 0 : undefined}
+      onClick={onClick}
+      onKeyDown={onKeyDown}
+    >
+      {children}
+    </div>
+  );
 }

Then update the event handler types at lines 48-49:

 type CardContainerProps = {
   readonly isInteractive: boolean;
   readonly href?: string;
   readonly ariaLabel?: string;
-  readonly onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
-  readonly onKeyDown?: (event: KeyboardEvent<HTMLAnchorElement>) => void;
+  readonly onClick?: (event: MouseEvent<HTMLElement>) => void;
+  readonly onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;
   readonly children: ReactNode;
 };

And update the handler signatures at lines 216 and 233:

-  const handleCardClick = useCallback(
-    (event: MouseEvent<HTMLAnchorElement>) => {
+  const handleCardClick = useCallback(
+    (event: MouseEvent<HTMLElement>) => {
-  const handleCardKeyDown = useCallback(
-    (event: KeyboardEvent<HTMLAnchorElement>) => {
+  const handleCardKeyDown = useCallback(
+    (event: KeyboardEvent<HTMLElement>) => {
🧹 Nitpick comments (1)
components/waves/list/WaveItem.tsx (1)

237-237: Simplify keyboard key check.

The check for event.key === "Space" is unnecessary. The spacebar key returns " " (a single space character), not the string "Space". Checking for "Space" has no effect.

Apply this diff:

-      if (event.key === "Enter" || event.key === " " || event.key === "Space") {
+      if (event.key === "Enter" || event.key === " ") {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d498bee and 171f4ad.

📒 Files selected for processing (5)
  • __tests__/components/waves/list/WaveItemDropped.test.tsx (1 hunks)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0016.md (0 hunks)
  • codex/tickets/TKT-0018.md (1 hunks)
  • components/waves/list/WaveItem.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • codex/tickets/TKT-0016.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/components/waves/list/WaveItemDropped.test.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/list/WaveItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/waves/list/WaveItem.tsx
🧬 Code graph analysis (1)
components/waves/list/WaveItem.tsx (5)
helpers/Helpers.ts (2)
  • getRandomColorWithSeed (736-750)
  • numberWithCommas (83-100)
helpers/navigation.helpers.ts (1)
  • getWaveRoute (23-71)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/list/WaveItemDropped.tsx (1)
  • WaveItemDropped (6-68)
components/waves/list/WaveItemFollow.tsx (1)
  • WaveItemFollow (22-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

@ragnep ragnep merged commit 4ad09f7 into main Oct 28, 2025
9 checks passed
@ragnep ragnep deleted the ui-ux-fixes branch October 28, 2025 11:09
This was referenced Oct 28, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Nov 17, 2025
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