Skip to content

Fix/landing page guidelines - #13

Open
julianromli wants to merge 4 commits into
mainfrom
fix/landing-page-guidelines
Open

Fix/landing page guidelines#13
julianromli wants to merge 4 commits into
mainfrom
fix/landing-page-guidelines

Conversation

@julianromli

@julianromli julianromli commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added keyboard-accessible "Skip to main content" link for improved navigation
    • Implemented system reduced-motion preference support across animations and transitions
    • Enhanced FAQ accordion and filter dropdown controls with improved keyboard accessibility
    • Expanded internationalization support for additional UI elements
  • Bug Fixes

    • Improved testimonials display and rendering
    • Better handling of unavailable video dates

@vercel

vercel Bot commented Apr 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vibedevid Ready Ready Preview, Comment, Open in v0 Apr 2, 2026 4:55am

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances accessibility by adding support for reduced-motion preferences across animated components, expands internationalization coverage for home page metadata and navigation, improves semantic HTML and ARIA attributes for better screen reader support, refactors type safety by introducing proper TypeScript enums instead of string literals, extracts hardcoded constants, and makes the testimonials component more flexible by making the image field optional.

Changes

Cohort / File(s) Summary
Internationalization & Navigation
app/home-page-client.tsx, messages/en.json, messages/id.json
Added i18n support for organization metadata (contactType, audienceType, keywords), FAQ items, and navigation strings (skipToMainContent, toggleMenu). Introduced skip-to-main-content accessible anchor and wrapped main sections in semantic <main id="main-content"> element. Added new translation keys for dateUnavailable and projectList filter.
Accessibility - Reduced Motion Support
components/sections/hero-section.tsx, components/ui/navbar.tsx, components/ui/infinite-slider.tsx, components/ui/youtube-video-showcase.tsx, components/ui/testimonials-columns.tsx
Added useReducedMotion() or window.matchMedia('prefers-reduced-motion') checks to detect user motion preferences. When reduced motion is enabled, components immediately apply animations or disable transitions via motion-reduce:* Tailwind classes instead of using timer-based staggering. Updated animation effects and transition timings to respect accessibility preferences.
Accessibility - Semantic HTML & ARIA
components/sections/faq-section.tsx, components/ui/filter-controls.tsx, components/sections/project-showcase.tsx
Enhanced accessibility with explicit <button> controls for accordion toggling, added listbox/option ARIA roles with aria-expanded, aria-controls, aria-selected, and aria-labelledby attributes. Replaced <div> with semantic <section> for accordion content. Generated stable element IDs via useId() for proper ARIA wiring.
Type Safety & Constants
lib/constants/project-filters.ts, types/homepage.ts, hooks/useProjectFilters.ts, app/page.tsx, components/sections/project-showcase.tsx
Extracted hardcoded 'All' filter value to new constant ALL_PROJECT_FILTER_VALUE ('__all__'). Updated ProjectFilters.selectedTrending from generic string to typed SortBy enum ('trending' | 'top' | 'newest'). Made Testimonial.image optional. Refactored project filter hook to use new types and avoid switch-statement conversions for trending values.
Content Structure Updates
components/sections/reviews-section.tsx
Removed hardcoded image array and optional image field from testimonials. Removed third testimonial column instance on lg screens (keeping only two visible columns). Updated Suspense skeleton to use stable string IDs instead of numeric indices.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 With motion whispers gently hushed,
And aria-labels softly rushed,
These translations help the world align,
While types keep structure neat and fine.
Accessibility hops along the way,
Making web more accessible today! 🌟

🚥 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 'Fix/landing page guidelines' is vague and does not clearly convey the actual changes made. The pull request involves extensive improvements across multiple components including i18n support, accessibility enhancements (ARIA attributes), reduced-motion support, type safety improvements, and UI refinements. The title does not meaningfully describe any of these primary changes. Replace with a more descriptive title that captures the main changes, such as 'Add i18n support and accessibility improvements to landing page components' or 'Enhance landing page with translations, ARIA attributes, and reduced-motion support'.
✅ 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 fix/landing-page-guidelines

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/ui/infinite-slider.tsx">

<violation number="1" location="components/ui/infinite-slider.tsx:30">
P3: `setKey` is now dead state churn: it triggers re-renders but no logic depends on it after `key` was removed from the effect dependencies.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const translation = useMotionValue(0)
const [isTransitioning, setIsTransitioning] = useState(false)
const [key, setKey] = useState(0)
const [, setKey] = useState(0)

@cubic-dev-ai cubic-dev-ai Bot Apr 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: setKey is now dead state churn: it triggers re-renders but no logic depends on it after key was removed from the effect dependencies.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/ui/infinite-slider.tsx, line 30:

<comment>`setKey` is now dead state churn: it triggers re-renders but no logic depends on it after `key` was removed from the effect dependencies.</comment>

<file context>
@@ -27,10 +27,16 @@ export function InfiniteSlider({
   const translation = useMotionValue(0)
   const [isTransitioning, setIsTransitioning] = useState(false)
-  const [key, setKey] = useState(0)
+  const [, setKey] = useState(0)
+  const reducedMotion = useReducedMotion()
 
</file context>
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
components/ui/youtube-video-showcase.tsx (1)

156-165: ⚠️ Potential issue | 🟡 Minor

Inline styles override motion-reduce: utility, preventing reduced-motion users from seeing a static first paint.

The motion-reduce:transform-none utility on line 156 has lower CSS specificity than the inline transform style on line 161, so it never neutralizes the animation. Additionally, because animatedVideos starts empty and is only populated in the effect (line 248), all cards render hidden initially, then reveal after mount—even for reduced-motion users checking the preference on line 246.

To fix this, consider moving the reduced-motion check to initial state or use CSS @media (prefers-reduced-motion: reduce) rules to prevent the hidden state on the first paint instead of relying on the state animation.

Also applies to: 244–249

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

In `@components/ui/youtube-video-showcase.tsx` around lines 156 - 165, The inline
transform/opacity styles on the video-option override the motion-reduce utility
and hide cards on first paint; fix by honoring the user's reduced-motion
preference at render/init instead of relying on the effect: detect
prefers-reduced-motion (e.g., via window.matchMedia('(prefers-reduced-motion:
reduce)') or CSS `@media`) when initializing animatedVideos so it contains all
indexes for reduced-motion users (or alternatively apply a CSS `@media`
(prefers-reduced-motion: reduce) rule that forces transform: none and opacity: 1
for the .video-option selector), and conditionally skip the animation logic in
the effect that populates animatedVideos so the inline styles do not hide the
cards on first paint (update references to animatedVideos, isActive, and the
effect that populates animatedVideos).
🤖 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/sections/faq-section.tsx`:
- Around line 52-60: The current markup places <h3> inside the <button>, which
violates HTML5 and a11y rules; refactor the JSX so the heading wraps the button:
change the structure from <button>...<h3>{faq.question}</h3>...</button> to <h3
id={`faq-question-${index}`}><button type="button" onClick={() =>
toggleFAQ(index)} aria-expanded={openFAQ === index}
aria-controls={`faq-answer-${index}`}>{faq.question}</button></h3>, preserving
classes (move "flex w-full cursor-pointer items-center justify-between p-6" to
the button or adjust as needed) and keep the existing toggleFAQ, openFAQ, and
id/aria-controls/aria-expanded attributes unchanged.

In `@components/sections/project-showcase.tsx`:
- Around line 66-96: The component advertises listbox semantics (role="listbox",
role="option", aria-selected) but lacks keyboard/focus handling; update the
dropdown to implement proper listbox interactions by adding keyboard handlers
(ArrowUp/ArrowDown/Home/End/Escape/Enter) and focus management using
aria-activedescendant and a tracked active index, or remove listbox roles and
switch to a simple button/menu pattern. Specifically, modify the component that
renders the Button, uses isOpen, listboxId, options, selectedTrending, onChange,
setIsOpen, menuClassName and ChevronDown: either implement arrow-key navigation
to update an activeIndex state, set aria-activedescendant to the active option
id, move focus into the list when opened, handle Enter to select (calling
onChange) and Escape to close, and ensure option elements have matching ids; or
replace role="listbox"/role="option"/aria-selected with appropriate menu/aria
attributes and keep only click/tab activation.

In `@components/ui/filter-controls.tsx`:
- Around line 42-63: This custom dropdown in filter-controls.tsx (using isOpen,
listboxId, role="listbox", role="option", filterOptions, selectedFilter, Button,
ChevronDown) should be replaced with the existing Radix-based primitives: import
and use the Select (components/ui/select.tsx) or DropdownMenu
(components/ui/dropdown-menu.tsx) component instead of manual aria roles and
isOpen state; map filterOptions to the provided Select.Item or DropdownMenuItem
equivalents, wire selection via the Select onValueChange / DropdownMenu onSelect
to update selectedFilter, keep the Button/ChevronDown as the Trigger if desired,
and remove manual keyboard/focus management and role attributes so the Radix
implementation handles accessibility and focus for you.

In `@components/ui/testimonials-columns.tsx`:
- Around line 14-20: Remove the JS-based prefers-reduced-motion override: delete
the prefersReducedMotion check and any assignments to element.style.animation
(the block that reads window.matchMedia('(prefers-reduced-motion:
reduce)').matches and sets element.style.animation to 'none' or `scroll-up
${duration}s ...`). Keep the rest of the scrolling logic if needed but do not
set inline animation styles from JavaScript (remove references to
prefersReducedMotion, element.style.animation, and the duration-based inline
assignment) so the existing `@media` (prefers-reduced-motion: reduce) CSS rule in
globals.css can control motion.

In `@hooks/useProjectFilters.ts`:
- Around line 72-85: Remove initialProjects from the useEffect dependency array
and stop using it as the fallback in the fetch error path: in the effect that
defines fetchProjects (the useEffect depending on authReady, selectedTrending,
selectedFilter, skipInitialFetch), remove initialProjects from the dependency
list and change the catch block that currently calls
setProjects(initialProjects) to either setProjects([]) or set an error state
(e.g., setFetchError(true)) so the UI can show a retry, instead of rehydrating
server-provided initialProjects; keep initialProjects only as bootstrap input
when initializing state outside the effect, not as a dependency or error
fallback.

---

Outside diff comments:
In `@components/ui/youtube-video-showcase.tsx`:
- Around line 156-165: The inline transform/opacity styles on the video-option
override the motion-reduce utility and hide cards on first paint; fix by
honoring the user's reduced-motion preference at render/init instead of relying
on the effect: detect prefers-reduced-motion (e.g., via
window.matchMedia('(prefers-reduced-motion: reduce)') or CSS `@media`) when
initializing animatedVideos so it contains all indexes for reduced-motion users
(or alternatively apply a CSS `@media` (prefers-reduced-motion: reduce) rule that
forces transform: none and opacity: 1 for the .video-option selector), and
conditionally skip the animation logic in the effect that populates
animatedVideos so the inline styles do not hide the cards on first paint (update
references to animatedVideos, isActive, and the effect that populates
animatedVideos).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e650319-e03d-4735-b72f-fcede0aa4d00

📥 Commits

Reviewing files that changed from the base of the PR and between 4dfb125 and 2a203d5.

📒 Files selected for processing (16)
  • app/home-page-client.tsx
  • app/page.tsx
  • components/sections/faq-section.tsx
  • components/sections/hero-section.tsx
  • components/sections/project-showcase.tsx
  • components/sections/reviews-section.tsx
  • components/ui/filter-controls.tsx
  • components/ui/infinite-slider.tsx
  • components/ui/navbar.tsx
  • components/ui/testimonials-columns.tsx
  • components/ui/youtube-video-showcase.tsx
  • hooks/useProjectFilters.ts
  • lib/constants/project-filters.ts
  • messages/en.json
  • messages/id.json
  • types/homepage.ts

Comment on lines +52 to 60
<button
type="button"
onClick={() => toggleFAQ(index)}
className="flex w-full cursor-pointer items-center justify-between p-6"
aria-expanded={openFAQ === index}
aria-controls={`faq-answer-${index}`}
id={`faq-question-${index}`}
>
<h3 className="text-left font-semibold">{faq.question}</h3>

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

🧩 Analysis chain

🏁 Script executed:

cat -n components/sections/faq-section.tsx | head -80

Repository: julianromli/vibedevid

Length of output: 3648


🌐 Web query:

HTML5 button element content model what elements can go inside button

💡 Result:

The HTML5 <button> element's content model is phrasing content, but with the restriction that there must be no interactive content descendants and no descendants with the tabindex attribute specified. If the <button> is the first child of a customizable select element, it may also contain zero or one <select> element. Phrasing content includes text and elements such as <a>, <abbr>, <b>, <bdi>, <bdo>, <br>, <cite>, <code>, <data>, <em>, <i>, <kbd>, <label>, <mark>, <output>, <progress>, <q>, <ruby>, <samp>, <small>, <span>, <strong>, <sub>, <sup>, <time>, <u>, <var>, <wbr>, and certain embedded content like <img> and <video> (without interactive features). Interactive elements like <a> (with href), <button>, <details>, <input>, <select>, <textarea> are prohibited inside <button>.

Citations:


🌐 Web query:

WAI-ARIA accordion pattern button heading structure best practice

💡 Result:

The best practice for the WAI-ARIA accordion pattern button heading structure, per official W3C ARIA Authoring Practices Guide (APG), is to wrap each accordion header button in a heading element (e.g., h2, h3) with an appropriate aria-level for the page's information architecture. The button is the only interactive element inside the heading; other persistent visual elements (e.g., icons, snippets) should be outside the heading. Example structure:

Accordion Header Title

Panel content...
Key attributes on button: aria-expanded (true/false), aria-controls (ID of panel). Optionally, use role="region" and aria-labelledby on the panel, but avoid if >6 panels to prevent landmark proliferation. This uses native HTML semantics where possible (heading + button), ensures proper keyboard navigation (Enter/Space to toggle), and maintains document outline for screen readers.

Citations:


Wrap the accordion button in the heading, not the other way around.

Line 60 nests <h3> inside <button>, which violates HTML5 content model rules—heading elements are not permitted inside buttons. This breaks heading navigation and document outline in assistive tech. Per W3C WAI-ARIA accordion pattern, the correct structure is <h3><button>…</button></h3>.

🔧 Suggested markup
-              <CardContent className="p-0">
-                <button
-                  type="button"
-                  onClick={() => toggleFAQ(index)}
-                  className="flex w-full cursor-pointer items-center justify-between p-6"
-                  aria-expanded={openFAQ === index}
-                  aria-controls={`faq-answer-${index}`}
-                  id={`faq-question-${index}`}
-                >
-                  <h3 className="text-left font-semibold">{faq.question}</h3>
-                  <div className="ml-4 flex-shrink-0 transition-transform duration-300 motion-reduce:transition-none">
+              <CardContent className="p-0">
+                <h3>
+                  <button
+                    type="button"
+                    onClick={() => toggleFAQ(index)}
+                    className="flex w-full cursor-pointer items-center justify-between p-6"
+                    aria-expanded={openFAQ === index}
+                    aria-controls={`faq-answer-${index}`}
+                    id={`faq-question-${index}`}
+                  >
+                    <span className="text-left font-semibold">{faq.question}</span>
+                    <div className="ml-4 flex-shrink-0 transition-transform duration-300 motion-reduce:transition-none">
                     {openFAQ === index ? (
                       <Minus className="text-muted-foreground h-5 w-5" />
                     ) : (
                       <Plus className="text-muted-foreground h-5 w-5" />
                     )}
-                  </div>
-                </button>
+                    </div>
+                  </button>
+                </h3>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/sections/faq-section.tsx` around lines 52 - 60, The current markup
places <h3> inside the <button>, which violates HTML5 and a11y rules; refactor
the JSX so the heading wraps the button: change the structure from
<button>...<h3>{faq.question}</h3>...</button> to <h3
id={`faq-question-${index}`}><button type="button" onClick={() =>
toggleFAQ(index)} aria-expanded={openFAQ === index}
aria-controls={`faq-answer-${index}`}>{faq.question}</button></h3>, preserving
classes (move "flex w-full cursor-pointer items-center justify-between p-6" to
the button or adjust as needed) and keep the existing toggleFAQ, openFAQ, and
id/aria-controls/aria-expanded attributes unchanged.

Comment on lines +66 to 96
aria-haspopup="listbox"
aria-expanded={isOpen}
aria-controls={listboxId}
>
{selectedTrending}
{options.find((option) => option.value === selectedTrending)?.label ?? options[0]?.label}
<ChevronDown className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</Button>

{isOpen && (
<div className={menuClassName}>
<div
id={listboxId}
role="listbox"
className={menuClassName}
>
<div className="p-2">
{options.map((option) => (
<button
key={option}
key={option.value}
type="button"
role="option"
aria-selected={selectedTrending === option.value}
onClick={() => {
onChange(option)
onChange(option.value)
setIsOpen(false)
}}
className={`hover:bg-muted w-full rounded-md px-3 py-2 text-left text-sm transition-colors ${
selectedTrending === option ? 'bg-muted text-foreground' : 'text-muted-foreground'
selectedTrending === option.value ? 'bg-muted text-foreground' : 'text-muted-foreground'
}`}
>
{option}
{option.label}
</button>

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

🧩 Analysis chain

🏁 Script executed:

# First, let's read the project-showcase.tsx file to see the actual implementation
cat -n components/sections/project-showcase.tsx | head -120

Repository: julianromli/vibedevid

Length of output: 4386


🏁 Script executed:

# Check if there are keyboard event handlers in the file
rg "onKeyDown|onKeyUp|onKeyPress|ArrowDown|ArrowUp|Home|End" components/sections/project-showcase.tsx -A 3 -B 3

Repository: julianromli/vibedevid

Length of output: 47


🏁 Script executed:

# Let's also check the entire TrendingDropdown component to see all its props and behavior
ast-grep --pattern $'function TrendingDropdown($_) {
  $$$
}'

Repository: julianromli/vibedevid

Length of output: 4018


This dropdown advertises listbox semantics without the required keyboard behavior.

Lines 66–96 set role="listbox", role="option", and aria-selected, but the component has no keyboard handlers for arrow navigation, home/end, or focus management. According to the WAI-ARIA listbox pattern, users expect to navigate options with arrow keys and manage focus via aria-activedescendant—currently only tab navigation and clicks work. Either implement the complete listbox interaction pattern or remove the listbox roles and use a combobox/menu pattern instead.

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

In `@components/sections/project-showcase.tsx` around lines 66 - 96, The component
advertises listbox semantics (role="listbox", role="option", aria-selected) but
lacks keyboard/focus handling; update the dropdown to implement proper listbox
interactions by adding keyboard handlers
(ArrowUp/ArrowDown/Home/End/Escape/Enter) and focus management using
aria-activedescendant and a tracked active index, or remove listbox roles and
switch to a simple button/menu pattern. Specifically, modify the component that
renders the Button, uses isOpen, listboxId, options, selectedTrending, onChange,
setIsOpen, menuClassName and ChevronDown: either implement arrow-key navigation
to update an activeIndex state, set aria-activedescendant to the active option
id, move focus into the list when opened, handle Enter to select (calling
onChange) and Escape to close, and ensure option elements have matching ids; or
replace role="listbox"/role="option"/aria-selected with appropriate menu/aria
attributes and keep only click/tab activation.

Comment on lines +42 to 63
aria-haspopup="listbox"
aria-expanded={isOpen}
aria-controls={listboxId}
>
Filter
{t('filter')}
<ChevronDown className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</Button>

{isOpen && (
<div className="bg-background border-border absolute top-full left-0 z-10 mt-2 w-48 rounded-lg border shadow-lg">
<div
id={listboxId}
role="listbox"
className="bg-background border-border absolute top-full left-0 z-10 mt-2 w-48 rounded-lg border shadow-lg"
>
<div className="p-2">
{filterOptions.map((option) => (
<button
key={option}
type="button"
role="option"
aria-selected={selectedFilter === option}
onClick={() => {

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

🧩 Analysis chain

🏁 Script executed:

# First, let's read the complete filter-controls.tsx file
cat -n components/ui/filter-controls.tsx

Repository: julianromli/vibedevid

Length of output: 2946


🏁 Script executed:

# Check if this file uses Radix components
rg -A 5 "from.*radix" components/ui/filter-controls.tsx

Repository: julianromli/vibedevid

Length of output: 47


🏁 Script executed:

# Look for focus management or keyboard event handlers in this file
rg "(useRef|focus|onKeyDown|onKeyUp|arrow|Escape)" components/ui/filter-controls.tsx

Repository: julianromli/vibedevid

Length of output: 47


🏁 Script executed:

# Check if there are existing listbox/dropdown/select primitives in components/ui/
fd -t f -e tsx -e ts components/ui/ | head -20

Repository: julianromli/vibedevid

Length of output: 426


🏁 Script executed:

# List all UI components to see what's available
ls -la components/ui/ | grep -E "\.(tsx|ts)$"

Repository: julianromli/vibedevid

Length of output: 5163


🏁 Script executed:

# Search for existing dropdown or select components
rg -l "dropdown|select|combobox" components/ui/ --type tsx --type ts

Repository: julianromli/vibedevid

Length of output: 92


🏁 Script executed:

# Check if Radix components are used elsewhere in the project
rg "from.*@radix-ui" components/ | head -20

Repository: julianromli/vibedevid

Length of output: 1747


🏁 Script executed:

# Check the select.tsx to understand how it's structured
head -80 components/ui/select.tsx

Repository: julianromli/vibedevid

Length of output: 3286


🏁 Script executed:

# Also check dropdown-menu.tsx for comparison
head -80 components/ui/dropdown-menu.tsx

Repository: julianromli/vibedevid

Length of output: 2534


Use the existing Select or DropdownMenu Radix primitive instead of building a custom filter dropdown.

The component adds listbox/option ARIA roles without implementing keyboard navigation or focus management, which misleads assistive technologies. More importantly, this violates the guideline to reuse existing Radix primitives. The codebase already provides components/ui/select.tsx and components/ui/dropdown-menu.tsx with proper keyboard support, focus handling, and ARIA semantics. Use one of those instead.

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

In `@components/ui/filter-controls.tsx` around lines 42 - 63, This custom dropdown
in filter-controls.tsx (using isOpen, listboxId, role="listbox", role="option",
filterOptions, selectedFilter, Button, ChevronDown) should be replaced with the
existing Radix-based primitives: import and use the Select
(components/ui/select.tsx) or DropdownMenu (components/ui/dropdown-menu.tsx)
component instead of manual aria roles and isOpen state; map filterOptions to
the provided Select.Item or DropdownMenuItem equivalents, wire selection via the
Select onValueChange / DropdownMenu onSelect to update selectedFilter, keep the
Button/ChevronDown as the Trigger if desired, and remove manual keyboard/focus
management and role attributes so the Radix implementation handles accessibility
and focus for you.

Comment on lines +14 to +20
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches

if (prefersReducedMotion) {
element.style.animation = 'none'
} else {
element.style.animation = `scroll-up ${duration}s linear infinite`
}

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:

cat -n components/ui/testimonials-columns.tsx

Repository: julianromli/vibedevid

Length of output: 3157


🏁 Script executed:

rg "motion-safe|motion-reduce" --type tsx --type ts

Repository: julianromli/vibedevid

Length of output: 92


🏁 Script executed:

rg "prefers-reduced-motion" --type tsx --type ts -B 3 -A 5

Repository: julianromli/vibedevid

Length of output: 92


🏁 Script executed:

rg "motion-safe|motion-reduce" -i

Repository: julianromli/vibedevid

Length of output: 2897


🏁 Script executed:

fd tailwind.config

Repository: julianromli/vibedevid

Length of output: 47


🏁 Script executed:

find . -name "globals.css" -o -name "tailwind.css" | head -5 | xargs cat

Repository: julianromli/vibedevid

Length of output: 21861


Remove the JavaScript effect and rely on the existing CSS media query.

The CSS already handles prefers-reduced-motion correctly at @media (prefers-reduced-motion: reduce) in globals.css, but the inline element.style.animation override (lines 16–20) has higher specificity and defeats it. This causes the animation to flash on first paint for reduced-motion users. Delete the effect entirely—the CSS media query is sufficient.

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

In `@components/ui/testimonials-columns.tsx` around lines 14 - 20, Remove the
JS-based prefers-reduced-motion override: delete the prefersReducedMotion check
and any assignments to element.style.animation (the block that reads
window.matchMedia('(prefers-reduced-motion: reduce)').matches and sets
element.style.animation to 'none' or `scroll-up ${duration}s ...`). Keep the
rest of the scrolling logic if needed but do not set inline animation styles
from JavaScript (remove references to prefersReducedMotion,
element.style.animation, and the duration-based inline assignment) so the
existing `@media` (prefers-reduced-motion: reduce) CSS rule in globals.css can
control motion.

Comment on lines +72 to +85
setProjects(initialProjects)
return
}

setProjects(fetchedProjects || [])
} catch (error) {
console.error('Error fetching projects:', error)
} catch {
setProjects(initialProjects)
} finally {
setLoading(false)
}
}

fetchProjects()
}, [authReady, selectedTrending, selectedFilter, skipInitialFetch])
}, [authReady, initialProjects, selectedTrending, selectedFilter, skipInitialFetch])

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

🧩 Analysis chain

🏁 Script executed:

# Find and examine useProjectFilters.ts
fd -type f -name "useProjectFilters.ts" -exec wc -l {} +

Repository: julianromli/vibedevid

Length of output: 235


🏁 Script executed:

# Find and examine app/page.tsx
fd -type f -name "page.tsx" -path "*/app/*" -exec wc -l {} +

Repository: julianromli/vibedevid

Length of output: 235


🏁 Script executed:

# Find useProjectFilters.ts
fd -t f "useProjectFilters.ts" && wc -l hooks/useProjectFilters.ts

Repository: julianromli/vibedevid

Length of output: 122


🏁 Script executed:

# Find app/page.tsx
fd -t f "page.tsx" -path "*/app/*" && wc -l app/page.tsx

Repository: julianromli/vibedevid

Length of output: 233


🏁 Script executed:

# Read the full useProjectFilters.ts
cat -n hooks/useProjectFilters.ts

Repository: julianromli/vibedevid

Length of output: 3683


🏁 Script executed:

# Find app/page.tsx
fd -t f "page.tsx" | grep app

Repository: julianromli/vibedevid

Length of output: 2130


🏁 Script executed:

# Read app/page.tsx with line numbers
cat -n app/page.tsx | head -130

Repository: julianromli/vibedevid

Length of output: 4532


Remove initialProjects from the useEffect dependency array and use it only as bootstrap data, not as a fallback.

When the fetch fails (lines 71-78), falling back to initialProjects shows data fetched with default trending sort and all categories, even if the user selected a different filter or sort. Additionally, initialProjects is a fresh array reference on each server render, so including it in the dependency array (line 85) causes unnecessary refetches when the user hasn't changed their filter or sort selection.

Instead, keep projects in an error state, show a retry option, or clear the selection if you must fall back to the initial data.

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

In `@hooks/useProjectFilters.ts` around lines 72 - 85, Remove initialProjects from
the useEffect dependency array and stop using it as the fallback in the fetch
error path: in the effect that defines fetchProjects (the useEffect depending on
authReady, selectedTrending, selectedFilter, skipInitialFetch), remove
initialProjects from the dependency list and change the catch block that
currently calls setProjects(initialProjects) to either setProjects([]) or set an
error state (e.g., setFetchError(true)) so the UI can show a retry, instead of
rehydrating server-provided initialProjects; keep initialProjects only as
bootstrap input when initializing state outside the effect, not as a dependency
or error fallback.

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