Fix/landing page guidelines - #13
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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 | 🟡 MinorInline styles override
motion-reduce:utility, preventing reduced-motion users from seeing a static first paint.The
motion-reduce:transform-noneutility on line 156 has lower CSS specificity than the inlinetransformstyle on line 161, so it never neutralizes the animation. Additionally, becauseanimatedVideosstarts 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
📒 Files selected for processing (16)
app/home-page-client.tsxapp/page.tsxcomponents/sections/faq-section.tsxcomponents/sections/hero-section.tsxcomponents/sections/project-showcase.tsxcomponents/sections/reviews-section.tsxcomponents/ui/filter-controls.tsxcomponents/ui/infinite-slider.tsxcomponents/ui/navbar.tsxcomponents/ui/testimonials-columns.tsxcomponents/ui/youtube-video-showcase.tsxhooks/useProjectFilters.tslib/constants/project-filters.tsmessages/en.jsonmessages/id.jsontypes/homepage.ts
| <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> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n components/sections/faq-section.tsx | head -80Repository: 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:
- 1: https://html.spec.whatwg.org/multipage/the-button-element.html
- 2: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button
- 3: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html
🌐 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
Citations:
- 1: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
- 2: https://www.aditus.io/patterns/accordion/
- 3: https://www.w3.org/TR/2019/NOTE-wai-aria-practices-1.1-20190814/examples/accordion/accordion.html
- 4: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/
- 5: https://webaim.org/techniques/disclosures
- 6: https://equalizedigital.com/how-to-code-accessible-accordions/
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.
| 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> |
There was a problem hiding this comment.
🧩 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 -120Repository: 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 3Repository: 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.
| 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={() => { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the complete filter-controls.tsx file
cat -n components/ui/filter-controls.tsxRepository: julianromli/vibedevid
Length of output: 2946
🏁 Script executed:
# Check if this file uses Radix components
rg -A 5 "from.*radix" components/ui/filter-controls.tsxRepository: 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.tsxRepository: 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 -20Repository: 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 tsRepository: julianromli/vibedevid
Length of output: 92
🏁 Script executed:
# Check if Radix components are used elsewhere in the project
rg "from.*@radix-ui" components/ | head -20Repository: julianromli/vibedevid
Length of output: 1747
🏁 Script executed:
# Check the select.tsx to understand how it's structured
head -80 components/ui/select.tsxRepository: julianromli/vibedevid
Length of output: 3286
🏁 Script executed:
# Also check dropdown-menu.tsx for comparison
head -80 components/ui/dropdown-menu.tsxRepository: 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.
| 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` | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n components/ui/testimonials-columns.tsxRepository: julianromli/vibedevid
Length of output: 3157
🏁 Script executed:
rg "motion-safe|motion-reduce" --type tsx --type tsRepository: julianromli/vibedevid
Length of output: 92
🏁 Script executed:
rg "prefers-reduced-motion" --type tsx --type ts -B 3 -A 5Repository: julianromli/vibedevid
Length of output: 92
🏁 Script executed:
rg "motion-safe|motion-reduce" -iRepository: julianromli/vibedevid
Length of output: 2897
🏁 Script executed:
fd tailwind.configRepository: julianromli/vibedevid
Length of output: 47
🏁 Script executed:
find . -name "globals.css" -o -name "tailwind.css" | head -5 | xargs catRepository: 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.
| 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]) |
There was a problem hiding this comment.
🧩 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.tsRepository: julianromli/vibedevid
Length of output: 122
🏁 Script executed:
# Find app/page.tsx
fd -t f "page.tsx" -path "*/app/*" && wc -l app/page.tsxRepository: julianromli/vibedevid
Length of output: 233
🏁 Script executed:
# Read the full useProjectFilters.ts
cat -n hooks/useProjectFilters.tsRepository: julianromli/vibedevid
Length of output: 3683
🏁 Script executed:
# Find app/page.tsx
fd -t f "page.tsx" | grep appRepository: julianromli/vibedevid
Length of output: 2130
🏁 Script executed:
# Read app/page.tsx with line numbers
cat -n app/page.tsx | head -130Repository: 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.
Summary by CodeRabbit
New Features
Bug Fixes