Supabase Wrapped 2025#41091
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
7 Skipped Deployments
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
🎭 Playwright Test ResultsDetails
Skipped testsFeatures › sql-editor.spec.ts › SQL Editor › snippet favourite works as expected |
This comment has been minimized.
This comment has been minimized.
|
Caution Review failedThe pull request is closed. WalkthroughIntroduces a new "Supabase Wrapped" feature page with multiple animated components and content sections displaying statistics, customer stories, product announcements, and AI insights. The feature includes an interactive page layout with animated counters, grid backgrounds, floating elements, and rotating image galleries using Framer Motion and newly added animation libraries. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (11)
apps/www/components/Wrapped/AnimatedCounter.tsx (3)
18-22: Refs not synchronized when props change.If
valueorincrementprops change after mount,countRef.currentretains its stale value. This could cause unexpected behavior if the component is reused with different data.Consider resetting refs when relevant props change:
const countRef = useRef(value) const lastUpdateRef = useRef(0) const isVisibleRef = useRef(false) const rafIdRef = useRef<number>() + // Sync countRef when value prop changes + useEffect(() => { + countRef.current = value + if (spanRef.current) { + spanRef.current.textContent = formatNumber(value) + } + }, [value, formatNumber])
36-71: RAF loop runs continuously even when not visible.The
requestAnimationFrameloop continues scheduling even when the element is off-screen (it just skips the update). For pages with many counters, consider pausing the RAF loop entirely when not visible:useEffect(() => { if (increment <= 0) return const tick = (timestamp: number) => { - if (isVisibleRef.current && timestamp - lastUpdateRef.current >= intervalMs) { + if (!isVisibleRef.current) { + rafIdRef.current = requestAnimationFrame(tick) + return + } + if (timestamp - lastUpdateRef.current >= intervalMs) { countRef.current += increment // ... } rafIdRef.current = requestAnimationFrame(tick) }Alternatively, start/stop the RAF loop based on visibility changes.
5-10: Naming collision with existingAnimatedCounterinpackages/ui.There's an existing
AnimatedCountercomponent inpackages/ui/src/components/AnimatedCounter/with a different API and purpose (animates to a target value). This component continuously increments, which is a distinct use case.Consider renaming to
IncrementingCounterorLiveCounterto avoid confusion when both are used in the codebase.Also applies to: 12-17
apps/www/components/Wrapped/AnimatedGridBackground.tsx (2)
39-44: Consider debouncing the resize handler.The resize listener fires on every resize event and updates state, potentially causing many re-renders during window resizing. Consider debouncing:
useEffect(() => { + let timeoutId: NodeJS.Timeout const checkMobile = () => setIsMobile(window.innerWidth < 768) + const debouncedCheck = () => { + clearTimeout(timeoutId) + timeoutId = setTimeout(checkMobile, 100) + } checkMobile() - window.addEventListener('resize', checkMobile) - return () => window.removeEventListener('resize', checkMobile) + window.addEventListener('resize', debouncedCheck) + return () => { + window.removeEventListener('resize', debouncedCheck) + clearTimeout(timeoutId) + } }, [])
136-140: Interval recreated on every tile move.Since
moveRandomTiledepends ontilePositions, the interval is cleared and recreated after every tile movement. This works but is inefficient. Consider using a ref to accesstilePositionsto keepmoveRandomTilestable:+ const tilePositionsRef = useRef(tilePositions) + useEffect(() => { + tilePositionsRef.current = tilePositions + }, [tilePositions]) const moveRandomTile = useCallback(() => { - const occupiedCells = getOccupiedCells() - const tileIds = Array.from(tilePositions.keys()) + const positions = tilePositionsRef.current + const occupiedCells = new Set(positions.values()) + const tileIds = Array.from(positions.keys()) // ... - }, [tilePositions, getAdjacentCells, getOccupiedCells]) + }, [getAdjacentCells])apps/www/components/Wrapped/Pages/YearOfAI.tsx (1)
1-1: Remove unused import.
Androidis imported but never used in this component.-import { Android } from '../Visuals'apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
75-78: Consider using thecolsvariable consistently.The
colsvariable is declared but only used for empty cell calculation. The grid layout hardcodeslg:grid-cols-2. Consider either removing the variable and hardcoding2in the calculation, or using a template literal/dynamic approach for the grid class if flexibility is intended.export const CustomerStories = () => { - const cols = 2 - return (And update line 112:
- const remainder = testimonials.length % cols - const emptyCells = remainder === 0 ? 0 : cols - remainder + const remainder = testimonials.length % 2 + const emptyCells = remainder === 0 ? 0 : 2 - remainderapps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx (2)
533-543: Consider using index instead of title for hover comparison.Line 539 compares
hoveredItem?.title === item.titleto determine hover state. While this works for the current data, using title for identity is fragile if titles are ever duplicated. Since the timeline items are static and ordered, consider comparing by index instead.Apply this diff:
- const [hoveredItem, setHoveredItem] = useState<TimelineItem | null>(null) + const [hoveredIndex, setHoveredIndex] = useState<number | null>(null) // ... {timelineItems.map((item, index) => ( <TimelinePoint key={`${item.title}-${index}`} item={item} index={index} - isHovered={hoveredItem?.title === item.title} - onMouseEnter={() => setHoveredItem(item)} - onMouseLeave={() => setHoveredItem(null)} + isHovered={hoveredIndex === index} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} /> ))}
499-547: Add webkit-specific scrollbar hiding for cross-browser compatibility.The inline styles hide scrollbars for Firefox (
scrollbarWidth: 'none') and IE (msOverflowStyle: 'none'), but webkit browsers (Chrome, Safari, Edge) will still show scrollbars.Add a CSS class with webkit scrollbar hiding:
<div - className="overflow-x-auto" + className="overflow-x-auto [&::-webkit-scrollbar]:hidden" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }} >Or add a global CSS class if this pattern is reused:
.hide-scrollbar { scrollbar-width: none; -ms-overflow-style: none; } .hide-scrollbar::-webkit-scrollbar { display: none; }apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
53-58: Consider more descriptive alt text for accessibility.The alt text is generic ("Supabase Select event photo") for all images. While this is acceptable, more descriptive alt text would improve accessibility. If these images represent specific moments or aspects of the event, consider passing more descriptive alt text as a prop.
apps/www/components/Wrapped/Pages/Intro.tsx (1)
175-203: Consider animating the Y offset for smoother transitions.Line 199 sets
transform: 'translateY(calc(-50% + 30px))'as an initial inline style, but the Scrollytelling animation only controlsopacityandfilter. The Y offset remains at +30px throughout the animation, creating a slight permanent downward shift.If the intent is to animate from this offset position back to center, add the Y transform to the animation tween:
<Scrollytelling.Animation key={index} tween={[ { start, end: fadeInEnd, - to: { opacity: 1, filter: 'blur(0px)' }, + to: { opacity: 1, filter: 'blur(0px)', y: 0 }, }, ...(index < titles.length - 1 ? [ { start: fadeOutStart, end, - to: { opacity: 0, filter: 'blur(4px)' }, + to: { opacity: 0, filter: 'blur(4px)', y: -30 }, }, ] : []), ]} > <p className="h1 text-center absolute inset-x-8 top-1/2 -translate-y-1/2" - style={{ opacity: 0, transform: 'translateY(calc(-50% + 30px))' }} + style={{ opacity: 0 }} >If the +30px offset is intentional throughout, this can be ignored.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (14)
apps/www/public/images/wrapped/rachelzphotographyllc-21.jpegis excluded by!**/*.jpegapps/www/public/images/wrapped/rachelzphotographyllc-91.jpegis excluded by!**/*.jpegapps/www/public/images/wrapped/rachelzphotographyllc1-143.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-163.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-190.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-202.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-216.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-229.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-247.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-250.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-252.JPGis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-3.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/rachelzphotographyllc1-99.jpgis excluded by!**/*.jpgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
apps/www/app/wrapped/page.tsx(1 hunks)apps/www/components/Wrapped/AnimatedCounter.tsx(1 hunks)apps/www/components/Wrapped/AnimatedGridBackground.tsx(1 hunks)apps/www/components/Wrapped/Logos.tsx(1 hunks)apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx(1 hunks)apps/www/components/Wrapped/Pages/CustomerStories.tsx(1 hunks)apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)apps/www/components/Wrapped/Pages/Home.tsx(1 hunks)apps/www/components/Wrapped/Pages/Intro.tsx(1 hunks)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx(1 hunks)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx(1 hunks)apps/www/components/Wrapped/Pages/YearOfAI.tsx(1 hunks)apps/www/components/Wrapped/Visuals.tsx(1 hunks)apps/www/package.json(1 hunks)apps/www/tailwind.config.js(1 hunks)
🧰 Additional context used
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use PageLayout component for new page structures with props: children, title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) when a page is within an existing section
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for multi-section page layouts
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Read packages/ui/index.tsx file for full list of available components before composing interfaces
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use Sheet component to reveal complicated forms or information relating to an object without context switching to a new page
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for page contents
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsxapps/www/components/Wrapped/Pages/Intro.tsxapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use PageLayout component for new page structures with props: children, title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsxapps/www/components/Wrapped/Pages/Intro.tsxapps/www/components/Wrapped/Pages/Home.tsxapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for multi-section page layouts
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsxapps/www/components/Wrapped/Pages/Intro.tsxapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use Card component to group related pieces of information
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use CardContent for card sections, CardFooter for actions, and CardHeader/CardTitle only when card content is not described by surrounding content or when using multiple Cards to group related pieces
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Studio-specific components related to individual pages should be placed in apps/studio/components/interfaces
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/tailwind.config.jsapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use Admonition component to alert users of important actions or restrictions
Applied to files:
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) when a page is within an existing section
Applied to files:
apps/www/components/Wrapped/Pages/Intro.tsxapps/www/tailwind.config.jsapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Place Admonition components at the top of page contents (below title) or at the top of related ScaffoldSection (below title), and use sparingly
Applied to files:
apps/www/components/Wrapped/Pages/Intro.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Never use Tailwind classes for colors; use custom defined color classes (bg, bg-muted, bg-warning, bg-destructive, text-foreground, text-foreground-light, text-foreground-lighter, text-warning, text-destructive)
Applied to files:
apps/www/tailwind.config.js
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use typography classes from apps/studio/styles/typography.scss instead of hard-coding Tailwind classes (e.g., use 'heading-default' instead of 'text-sm font-medium')
Applied to files:
apps/www/tailwind.config.js
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/projects/[ref]/**/*.{ts,tsx} : Project-related pages should be organized in apps/studio/pages/projects/[ref]
Applied to files:
apps/www/tailwind.config.js
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Studio-specific components should be placed in apps/studio/components directory
Applied to files:
apps/www/tailwind.config.jsapps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Next.js pages should be created in apps/studio/pages directory
Applied to files:
apps/www/tailwind.config.jsapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/components/ui/**/*.{ts,tsx} : Studio-specific generic UI components should be placed in apps/studio/components/ui
Applied to files:
apps/www/tailwind.config.jsapps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:39:45.932Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/docs-graphql.mdc:0-0
Timestamp: 2025-12-11T02:39:45.932Z
Learning: Applies to apps/docs/resources/**/*.ts : The `/apps/docs/resources` folder contains GraphQL endpoint architecture organized in a modular pattern where each top-level query is in its own folder with consistent file structure: `{queryName}Model.ts`, `{queryName}Schema.ts`, `{queryName}Resolver.ts`, `{queryName}Types.ts` (optional), and `{queryName}Sync.ts` (optional)
Applied to files:
apps/www/tailwind.config.js
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : UI components from packages/ui should be imported with _Shadcn_ suffix where applicable (e.g., Input_Shadcn_)
Applied to files:
apps/www/tailwind.config.jsapps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to packages/ui/**/*.{ts,tsx} : Do not introduce new UI components in packages/ui unless explicitly asked to; use existing shadcn/ui-based components
Applied to files:
apps/www/tailwind.config.jsapps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:39:45.932Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/docs-graphql.mdc:0-0
Timestamp: 2025-12-11T02:39:45.932Z
Learning: Applies to apps/docs/app/api/graphql/tests/**/*.test.ts : Test files for GraphQL queries should be located at `apps/docs/app/api/graphql/tests/{queryName}.test.ts` and include tests for: (1) requesting all fields including nested fields with assertion for no errors and proper field returns, and (2) error triggering with assertion for proper GraphQL error response
Applied to files:
apps/www/tailwind.config.js
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use Sheet component to reveal complicated forms or information relating to an object without context switching to a new page
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Wrap forms in a Card component unless specified otherwise
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Read packages/ui/index.tsx file for full list of available components before composing interfaces
Applied to files:
apps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Prefer _Shadcn_ components over non-suffixed variants when available
Applied to files:
apps/www/components/Wrapped/Visuals.tsx
📚 Learning: 2025-12-11T02:40:11.135Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T02:40:11.135Z
Learning: Use Next.js pages router for page routing
Applied to files:
apps/www/app/wrapped/page.tsx
🧬 Code graph analysis (10)
apps/www/components/Wrapped/Pages/Devs.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/Wrapped/AnimatedCounter.tsx (1)
AnimatedCounter(12-74)
apps/www/components/Wrapped/AnimatedCounter.tsx (1)
packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx (1)
AnimatedCounterProps(7-42)
apps/www/components/Wrapped/Pages/CustomerStories.tsx (2)
apps/www/components/Wrapped/Logos.tsx (7)
PhoenixEnergyLogo(3-21)RallyLogo(23-34)SoshiLogo(36-50)KayhanSpaceLogo(52-59)ResendLogo(61-68)DerivLogo(70-77)JuniverLogo(79-94)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (2)
apps/www/types/launch-week-6.ts (1)
Announcement(8-13)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
apps/www/components/Wrapped/Visuals.tsx (2)
Dots(18-28)Stripes(3-16)
apps/www/components/Wrapped/Pages/Intro.tsx (1)
apps/www/components/Wrapped/Visuals.tsx (2)
Dots(18-28)Stripes(3-16)
apps/www/components/Wrapped/Pages/Home.tsx (1)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/app/wrapped/page.tsx (7)
apps/www/components/Wrapped/Pages/Home.tsx (1)
Home(192-231)apps/www/components/Wrapped/Pages/Intro.tsx (1)
Intro(156-211)apps/www/components/Wrapped/Pages/YearOfAI.tsx (1)
YearOfAI(19-93)apps/www/components/Wrapped/Pages/Devs.tsx (1)
Devs(57-157)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
ProductAnnouncements(278-338)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
SupabaseSelect(65-113)apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
CustomerStories(75-164)
apps/www/components/Wrapped/Pages/YearOfAI.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/SurveyResults/SurveyStatCard.tsx (1)
SurveyStatCard(6-117)
🔇 Additional comments (19)
apps/www/tailwind.config.js (1)
11-11: LGTM! Necessary configuration for App Router support.The addition of
'./app/**/*.{tsx,ts,js}'to the Tailwind content configuration correctly enables class scanning for the new Wrapped feature components in the App Router directory structure.apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
170-231: LGTM!The grid rendering logic, Framer Motion animations, and tile transform calculations are well-implemented. The staggered entry animation and spring-based movement provide a polished visual effect.
apps/www/app/wrapped/page.tsx (1)
13-25: LGTM!The page composition is clean and straightforward. Each wrapped section is rendered in a logical sequence within the
DefaultLayout.apps/www/components/Wrapped/Pages/Devs.tsx (2)
6-55: LGTM!The stats configuration is well-structured. Items with decimal values (e.g.,
64.5 PB) correctly use thesuffixpattern to bypassAnimatedCounter, which handles the decimal display appropriately.
57-157: LGTM!The component is well-structured with clear separation between hero stats and grid stats. The responsive grid border handling and empty cell logic for alignment are correctly implemented.
apps/www/components/Wrapped/Visuals.tsx (1)
1-16: LGTM!The
Stripescomponent uses clean CSS-only gradients, and theAndroidSVG component correctly forwards props usingComponentProps<'svg'>. Both are well-implemented presentational components.Also applies to: 30-37
apps/www/components/Wrapped/Pages/YearOfAI.tsx (1)
19-93: LGTM!The component is well-structured with consistent layout patterns matching other Wrapped pages. The use of
SurveyStatCardwithlabelandpercentprops aligns with the component's expected interface.apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
116-150: LGTM!The testimonial grid implementation is solid with proper accessibility considerations—external links use
target="_blank"withrel="noopener noreferrer", and the grid layout with empty cell padding ensures visual consistency.apps/www/components/Wrapped/Pages/Home.tsx (1)
192-231: LGTM!The Home component composition is clean, with proper use of AnimatedGridBackground and FloatingStatBubbles. The responsive layout with viewport height calculation is appropriate for a hero section.
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (3)
227-239: Placeholder URLs detected.December 2025 announcements all point to
https://supabase.com/blogwhich appears to be a placeholder. Verify these are intentional placeholders for future content, or update with actual URLs before release.
310-333: LGTM!The month reordering algorithm correctly implements a top-to-bottom flow in a two-column layout. The logic interleaves months from the first and second halves of the array to achieve newspaper-style column ordering.
242-276: LGTM!The MonthSection component is well-structured with proper external link handling (
target="_blank"withrel="noopener noreferrer"). The Launch Week badge rendering is clean.apps/www/components/Wrapped/Logos.tsx (1)
1-94: LGTM!Clean SVG logo component implementations. All components correctly:
- Accept and spread
React.ComponentProps<'svg'>for flexibility- Use
fill="currentColor"enabling parent-controlled theming- Have proper
viewBoxattributes for scalabilityapps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx (2)
1-14: LGTM! Clean type definition and imports.The component structure and type definitions are well-organized.
343-392: Position calculation logic is correct.The gap calculation properly accounts for spacing both within and between month groups. The formula
totalItemGaps = timelineItems.length - months.lengthcorrectly computes gaps within months by excluding the first item of each month.apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
65-113: LGTM! Clean component composition.The layout structure properly separates concerns between hero, description, and gallery sections.
apps/www/components/Wrapped/Pages/Intro.tsx (3)
1-26: LGTM! Clean setup with well-defined types.The constants and type definitions are clear and appropriate for the component's needs.
28-81: LGTM! Robust collision detection and box generation.The helper functions properly handle edge cases, including returning
nullwhen a non-overlapping position cannot be found after multiple attempts. The drift distribution provides good visual variety.
127-154: LGTM! Smooth animations with proper cleanup on exit.The animation configuration and component rendering are well-structured with appropriate framer-motion patterns.
| { | ||
| title: 'Supabase ETL', | ||
| description: 'Private Alpha', | ||
| date: new Date('2025-12-02'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'Analytics Buckets', | ||
| description: 'Public Alpha', | ||
| date: new Date('2025-12-03'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'Vector Buckets', | ||
| description: 'Public Alpha', | ||
| date: new Date('2025-12-04'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'New Auth Templates', | ||
| date: new Date('2025-12-05'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'Supabase OAuth Server', | ||
| date: new Date('2025-12-06'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'Supabase in AWS Marketplace', | ||
| date: new Date('2025-12-07'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'NodeJS Edge Functions support', | ||
| date: new Date('2025-12-08'), | ||
| url: 'https://supabase.com/blog', | ||
| }, | ||
| { | ||
| title: 'Edge Functions download without Docker', | ||
| date: new Date('2025-12-09'), | ||
| url: 'https://supabase.com/blog', | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify the file exists and read the specific lines
head -c 100 "apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx" 2>/dev/null && echo "File exists"Repository: supabase/supabase
Length of output: 172
🏁 Script executed:
# Get the total line count and read the target lines
wc -l "apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx"Repository: supabase/supabase
Length of output: 124
🏁 Script executed:
# Read lines 298-340 to verify the content
sed -n '298,340p' "apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx"Repository: supabase/supabase
Length of output: 1104
🏁 Script executed:
# Check for TODO comments or related markers around these lines
sed -n '290,350p' "apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx"Repository: supabase/supabase
Length of output: 1587
🏁 Script executed:
# Search for any other instances of placeholder URLs or TODO comments in the file
grep -n "TODO\|FIXME\|placeholder\|supabase.com/blog" "apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx" | head -20Repository: supabase/supabase
Length of output: 1167
Update December announcement URLs with specific blog post links.
Lines 298–340 contain December 2025 announcement items that use a generic https://supabase.com/blog URL, which differs from the established pattern in earlier months. All prior timeline items reference specific blog posts (e.g., https://supabase.com/blog/jwt-signing-keys, https://supabase.com/blog/storage-v4). Once these announcements are published, update these December items with their corresponding blog post URLs.
🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx around lines 298
to 340 the December 2025 announcement entries use the generic URL
"https://supabase.com/blog" instead of the specific blog post URLs used
elsewhere; update each object’s url property to point to the final, published
blog post slug (e.g., https://supabase.com/blog/<post-slug>) for "Supabase ETL",
"Analytics Buckets", "Vector Buckets", "New Auth Templates", "Supabase OAuth
Server", "Supabase in AWS Marketplace", "NodeJS Edge Functions support", and
"Edge Functions download without Docker" as soon as the posts are live, ensuring
the URL pattern matches the earlier entries (full path with slug) and validating
each link resolves; if a post isn’t published yet, leave a TODO comment or keep
a clearly marked placeholder until the final slug is available.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Fix the error for babel/preset-typescript in docs. * Unfix the version. * Try using amaro for type stripping. * Run prettier after stripping types. * Fix tests. --------- Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
Change the isPending state to isLoading.
* Remove emoty fields * Fix lock file
* added table advisor query * updated to include table editor performance * updated JSON B * added side panel * updated query indexes to show highlights context * show index advisor in table editor * updated invalidation logic * added color updates * added query indexes * updated query performance type * updated overflow and title * put behind flag * remove gap * added on close * Update apps/studio/data/database/table-index-advisor-query.ts Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * updated styling --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
* fix flag lib * updated uneeded logic * updated logic * remove line * updated comment
* Hide favorite and share actions for self-hosted version. * Rename the query on save only on platform. * Simplify useCheckOpenAiKeyQuery. * Rename with AI now depends if the OPENAI_API_KEY is set. * Minor fixes. * Fix the tests to use .skip for skipping tests. Remove extra port params. * Make the test for favourites work only on platform variant.
* handle new granularity * undo unnecessary type change * rm console log
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/www/app/wrapped/page.tsx (1)
11-26: Rename theProductsCta2import to match the file/component for readability.
Line 11 importsProductsCta2but aliases it asProductsCta, which is easy to misread during maintenance (especially alongside any existingProductsCta).-import ProductsCta from '~/components/Sections/ProductsCta2' +import ProductsCta2 from '~/components/Sections/ProductsCta2' ... - <ProductsCta + <ProductsCta2 currentProduct="functions" className="max-w-[60rem] mx-auto px-6 w-full !py-24 xl:!gap-24 lg:!py-44" />apps/www/components/Wrapped/Pages/Devs.tsx (1)
130-185: Avoid allocatingIntl.NumberFormatinside the render loop.
Lines 153-160 create a formatter per cell per render; consider hoisting to module scope (or memoizing two formatters: integer vs decimal) and reusing.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/www/app/wrapped/page.tsx(1 hunks)apps/www/components/Sections/ProductsCta2.tsx(1 hunks)apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Use CardContent for card sections, and CardFooter for actions within cards
Applied to files:
apps/www/components/Sections/ProductsCta2.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Components should ideally be under 200-300 lines; break down large components with multiple distinct UI sections, complex conditional rendering, or multiple unrelated useState hooks
Applied to files:
apps/www/components/Sections/ProductsCta2.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for pages with multiple sections
Applied to files:
apps/www/components/Sections/ProductsCta2.tsxapps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
Applied to files:
apps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Applied to files:
apps/www/app/wrapped/page.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
Applied to files:
apps/www/app/wrapped/page.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.001Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.001Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Place pages in apps/studio/pages directory, with project-related pages in apps/studio/pages/projects/[ref] and organization-related pages in apps/studio/pages/org/[slug]
Applied to files:
apps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Add table actions to primary and secondary actions of PageLayout if table is main content of a page without search or filters
Applied to files:
apps/www/app/wrapped/page.tsx
📚 Learning: 2025-12-12T05:20:17.409Z
Learnt from: joshenlim
Repo: supabase/supabase PR: 41258
File: apps/studio/pages/project/[ref]/storage/vectors/buckets/[bucketId].tsx:9-9
Timestamp: 2025-12-12T05:20:17.409Z
Learning: In apps/studio/**/*.{ts,tsx}, use named import for DefaultLayout: `import { DefaultLayout } from 'components/layouts/DefaultLayout'` instead of default import. This is the new practice being adopted across the studio app.
Applied to files:
apps/www/app/wrapped/page.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Extract repeated JSX patterns into reusable components instead of copying similar JSX blocks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.001Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.001Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Do not introduce new UI components unless asked to; check packages/ui/index.tsx for available components before composing interfaces
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Define prop interfaces explicitly for React components with proper typing of props and callback handlers
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
🧬 Code graph analysis (1)
apps/www/components/Wrapped/Pages/Devs.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/Wrapped/AnimatedCounter.tsx (1)
AnimatedCounter(12-74)
🔇 Additional comments (3)
apps/www/components/Sections/ProductsCta2.tsx (1)
26-28:w-maxis fine here; just ensure no small-screen overflow if copy changes.This is a safe visual tweak with current heading text, but
w-maxcan cause horizontal overflow if marketing copy becomes longer or localized. Considermax-w-full(or removingw-max) if overflow is observed in responsive previews.apps/www/components/Wrapped/Pages/Devs.tsx (1)
78-92: VerifyAnimatedGridBackgroundhandlesrows={{ mobile, desktop }}correctly for “last row” border logic.
This page depends on the responsive rows feature (Line 82); please double-check the grid’s bottom-border removal behaves correctly on mobile vs desktop.apps/www/app/wrapped/page.tsx (1)
1-22: No client/server boundary issue here—all components are Client Components.DefaultLayout, Home, Intro, YearOfAI, Devs, SupabaseSelect, CustomerStories, ProductAnnouncements, and ProductsCta all have
'use client'directives, so mixing them in a'use client'page is valid and causes no build-time errors. This is a standard pattern in Next.js App Router.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/www/app/wrapped/WrappedClient.tsx (1)
13-29: LGTM! Clean composition structure.The component effectively composes multiple sections into a cohesive wrapped experience with a logical narrative flow. The
currentProduct="functions"value is correct (matchesPRODUCT_SHORTNAMES.FUNCTIONS), though consider using the enum constant for consistency with other product pages likeedge-functions.tsx.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/www/public/images/wrapped/wrapped-og.pngis excluded by!**/*.png
📒 Files selected for processing (3)
apps/www/app/wrapped/WrappedClient.tsx(1 hunks)apps/www/app/wrapped/page.tsx(1 hunks)apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/www/app/wrapped/page.tsx
- apps/www/components/Wrapped/Pages/Devs.tsx
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
📚 Learning: 2025-12-12T05:20:17.409Z
Learnt from: joshenlim
Repo: supabase/supabase PR: 41258
File: apps/studio/pages/project/[ref]/storage/vectors/buckets/[bucketId].tsx:9-9
Timestamp: 2025-12-12T05:20:17.409Z
Learning: In apps/studio/**/*.{ts,tsx}, use named import for DefaultLayout: `import { DefaultLayout } from 'components/layouts/DefaultLayout'` instead of default import. This is the new practice being adopted across the studio app.
Applied to files:
apps/www/app/wrapped/WrappedClient.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
Applied to files:
apps/www/app/wrapped/WrappedClient.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Applied to files:
apps/www/app/wrapped/WrappedClient.tsx
🧬 Code graph analysis (1)
apps/www/app/wrapped/WrappedClient.tsx (6)
apps/www/components/Wrapped/Pages/Intro.tsx (1)
Intro(156-211)apps/www/components/Wrapped/Pages/YearOfAI.tsx (1)
YearOfAI(19-93)apps/www/components/Wrapped/Pages/Devs.tsx (1)
Devs(75-190)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
ProductAnnouncements(291-351)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
SupabaseSelect(65-113)apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
CustomerStories(75-164)
🔇 Additional comments (2)
apps/www/app/wrapped/WrappedClient.tsx (2)
1-1: LGTM! Correct use of client directive.The
'use client'directive is properly placed and necessary for this component, as the composed sections include interactive elements and animations.
3-11: LGTM! Clean import structure.The imports use consistent path aliases and all imported components are utilized in the composition.
- Update AnimatedCounter to correctly reflect value changes in the DOM. - Add lifetime management for timers in FloatingStatBubbles and FloatingBoxes to prevent memory leaks. - Ensure cleanup of timers on component unmount for better resource management.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
102-103: Typo in comment: "wuth" should be "with".- {/* NOTE (Alan): extra padding here to optically align subheading wuth header.*/} + {/* NOTE (Alan): extra padding here to optically align subheading with header. */}apps/www/components/Wrapped/Pages/Devs.tsx (1)
27-73: Remove(stat as any)by defining proper types forgridStats.Line 152 uses
(stat as any).prefixwhich bypasses TypeScript's type checking. Define explicit types:+type GridStat = { + headline: string + number: number + increment: number + suffix?: string + prefix?: string +} + -const gridStats = [ +const gridStats: GridStat[] = [ { headline: 'Peak concurrent Realtime connections', ...Then on line 152:
- {(stat as any).prefix} + {stat.prefix}apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
320-347: Fix unusedoriginalIndexand make last-row border logic robust for odd month countsThis issue was previously identified:
originalIndexis computed but never used, and the border logicindex >= reordered.length - 2assumes an even number of months, which could fail with odd counts.
🧹 Nitpick comments (5)
apps/www/components/Wrapped/AnimatedCounter.tsx (2)
52-61: Consider pausing RAF loop when element is not visible.The RAF loop runs continuously and checks visibility on each frame, which is fine but slightly wasteful. For better efficiency, consider stopping the loop when not visible and restarting on visibility change:
+ useEffect(() => { + if (!isVisibleRef.current) { + if (rafIdRef.current) { + cancelAnimationFrame(rafIdRef.current) + rafIdRef.current = undefined + } + return + } + // ... start loop + }, [isVisibleRef.current, ...])Alternatively, using
setIntervalgated by visibility might be simpler for this use case. Current implementation works correctly, this is purely an optimization suggestion.
24-34: Minor: Consider memoizing the Intl.NumberFormat instance.Creating a new
Intl.NumberFormaton every call adds minor overhead. Sincecompactis the only variable, you could memoize the formatter:+ const formatter = useMemo( + () => + compact + ? new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }) + : null, + [compact] + ) + const formatNumber = useCallback( (num: number) => { - if (compact) { - return Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format( - num - ) - } - return num.toLocaleString() + return formatter ? formatter.format(num) : num.toLocaleString() }, - [compact] + [formatter] )apps/www/components/Wrapped/Pages/Devs.tsx (1)
133-136: Consider extractingcolsto match the grid definition.The
colsconstant is defined inside the IIFE (line 134) but the grid useslg:grid-cols-4(line 132). For clarity and to avoid mismatch, consider defining it alongside the grid:{/* Stats grid */} <div className="relative max-w-[60rem] mx-auto border-x"> - <div className="grid grid-cols-2 lg:grid-cols-4"> + <div className="grid grid-cols-2 lg:grid-cols-4"> {/* cols=4 for empty cell calculation */} {(() => { const cols = 4Or extract the calculation outside the render. This is a minor clarity improvement.
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
53-58: Addsizesprop to optimize image loading.When using
fillprop withnext/image, addingsizeshelps Next.js optimize image delivery. Without it, Next.js defaults to 100vw which may load unnecessarily large images.<Image src={currentImage} alt="Supabase Select event photo" fill className="object-cover" + sizes="(max-width: 1024px) 100vw, 40vw" />Adjust the sizes value to match your actual layout breakpoints and the parent container's width at each breakpoint.
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
102-129: Consider Fisher-Yates shuffle for better randomness.The tile selection uses
[...tileIds].sort(() => Math.random() - 0.5)on Line 107, which is a common but statistically biased shuffle approach. While this works fine for the visual effect here, the Fisher-Yates algorithm would provide more uniform randomization.Apply this diff if you want more uniform shuffling:
- // Shuffle tile IDs to pick randomly - const shuffledIds = [...tileIds].sort(() => Math.random() - 0.5) + // Shuffle tile IDs to pick randomly (Fisher-Yates) + const shuffledIds = [...tileIds] + for (let i = shuffledIds.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[shuffledIds[i], shuffledIds[j]] = [shuffledIds[j], shuffledIds[i]] + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/www/components/Wrapped/AnimatedCounter.tsx(1 hunks)apps/www/components/Wrapped/AnimatedGridBackground.tsx(1 hunks)apps/www/components/Wrapped/Pages/CustomerStories.tsx(1 hunks)apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)apps/www/components/Wrapped/Pages/Home.tsx(1 hunks)apps/www/components/Wrapped/Pages/Intro.tsx(1 hunks)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx(1 hunks)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx(1 hunks)apps/www/components/Wrapped/Pages/YearOfAI.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/www/components/Wrapped/Pages/YearOfAI.tsx
- apps/www/components/Wrapped/Pages/Intro.tsx
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Components should ideally be under 200-300 lines; break down large components with multiple distinct UI sections, complex conditional rendering, or multiple unrelated useState hooks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/CustomerStories.tsxapps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Extract repeated JSX patterns into reusable components instead of copying similar JSX blocks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-12T05:20:17.409Z
Learnt from: joshenlim
Repo: supabase/supabase PR: 41258
File: apps/studio/pages/project/[ref]/storage/vectors/buckets/[bucketId].tsx:9-9
Timestamp: 2025-12-12T05:20:17.409Z
Learning: In apps/studio/**/*.{ts,tsx}, use named import for DefaultLayout: `import { DefaultLayout } from 'components/layouts/DefaultLayout'` instead of default import. This is the new practice being adopted across the studio app.
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Use MultiSelector component from ui-patterns/multi-select for multi-select form fields
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for pages with multiple sections
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Add table actions to the right of ScaffoldSectionTitle if table is main content of a page section without search or filters
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Avoid inline arrow functions for expensive operations; use useCallback to maintain stable function references
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
🧬 Code graph analysis (4)
apps/www/components/Wrapped/Pages/Devs.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/Wrapped/AnimatedCounter.tsx (1)
AnimatedCounter(12-87)
apps/www/components/Wrapped/AnimatedCounter.tsx (1)
packages/ui/src/components/AnimatedCounter/AnimatedCounter.tsx (1)
AnimatedCounterProps(7-42)
apps/www/components/Wrapped/Pages/CustomerStories.tsx (3)
apps/www/components/Wrapped/Logos.tsx (6)
PhoenixEnergyLogo(3-21)RallyLogo(23-34)SoshiLogo(36-50)KayhanSpaceLogo(52-59)DerivLogo(70-77)JuniverLogo(79-94)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)blocks/vue/registry/default/lib/utils.ts (1)
cn(5-7)
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
⏰ 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). (3)
- GitHub Check: test (1)
- GitHub Check: test
- GitHub Check: typecheck
🔇 Additional comments (11)
apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)
75-163: LGTM!The component structure is clean. The grid border logic correctly handles the responsive 2-column layout, removing bottom borders from the last row on both mobile and desktop views. The empty cell generation properly fills incomplete rows.
apps/www/components/Wrapped/AnimatedCounter.tsx (1)
1-87: LGTM!The component correctly implements visibility-based counter animation with proper cleanup. The dual-effect pattern appropriately separates value synchronization from the animation loop setup. Note that this component serves a different purpose than the existing
AnimatedCounterinpackages/ui(incremental counting vs. animate-to-value), so both can coexist.apps/www/components/Wrapped/Pages/Devs.tsx (2)
111-128: LGTM!The hero stats section correctly uses
AnimatedCounterwith appropriate props. The varyingintervalMsvalues create visual dynamism.
75-189: LGTM on overall structure.The component correctly renders hero and grid sections with appropriate responsive layouts. The conditional rendering for suffix-based formatting vs animated counters is a reasonable approach.
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (3)
177-210: Timer cleanup issue has been addressed.The
isCancelledflag pattern correctly prevents state updates after unmount. The cleanup properly sets the flag and clears all tracked timeouts.
121-129: LGTM on image preloading.The preloading strategy correctly fires when the gallery enters view and prevents re-execution. Fire-and-forget is acceptable here since failed preloads will simply result in on-demand loading.
131-142: LGTM on slot initialization.The initialization correctly ensures each slot starts with a unique random image by sampling without replacement. With 4 slots and 13 images, there's sufficient diversity.
apps/www/components/Wrapped/Pages/Home.tsx (4)
105-161: Lifetime timer cleanup has been addressed.The
lifetimeTimersRefpattern correctly tracks all lifetime timeouts and clears them on unmount. The implementation follows the suggested fix from the past review.
42-99: LGTM on helper functions.The helper functions are well-designed:
getDistancecorrectly computes Euclidean distancegenerateNonOverlappingPositionhas a bounded retry limit (20 attempts) preventing infinite loopsgenerateRandomBubblegracefully falls back to any stat when all have been used
142-153: LGTM on spawn scheduling.The recursive
setTimeoutpattern correctly creates randomized spawn intervals. ClearingintervalIdon unmount breaks the chain since each timeout schedules its successor.
197-235: LGTM!The
Homecomponent is well-structured with clear separation between the animated background, floating bubbles, and content sections. The responsive row configuration (4 desktop / 8 mobile) provides appropriate visual density across screen sizes.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
336-358: TheoriginalIndexissue was fixed, but the fragile border logic remains.The unused
originalIndexfrom the past review has been removed. However, theindex >= reordered.length - 2heuristic forlg:border-b-0is still fragile—it works correctly only when the month count is even. If a month is added or removed later (making the count odd), the bottom border removal will apply incorrectly.Consider using row-based calculation for robustness:
{(() => { const half = Math.ceil(months.length / 2) const reordered: Month[] = [] for (let i = 0; i < half; i++) { reordered.push(months[i]) if (i + half < months.length) { reordered.push(months[i + half]) } } + const lastRow = Math.floor((reordered.length - 1) / 2) return reordered.map((month, index) => ( <div key={`desktop-${month.name}`} className={cn( 'hidden lg:block border-b border-muted lg:border-r', 'lg:[&:nth-child(2n)]:border-r-0', - index >= reordered.length - 2 && 'lg:border-b-0', + Math.floor(index / 2) === lastRow && 'lg:border-b-0', index === reordered.length - 1 && 'border-b-0' )} > <MonthSection month={month} /> </div> )) })()}apps/www/components/Wrapped/Pages/Devs.tsx (1)
6-73: Add type definitions forheroStatsandgridStats.The data arrays lack TypeScript type definitions, which leads to the
(stat as any).prefixcast at line 152. This was already flagged in a previous review.+type HeroStat = { + headline: string + number: number + increment: number + intervalMs: number +} + +type GridStat = { + headline: string + number: number + increment: number + suffix?: string + prefix?: string +} + -const heroStats = [ +const heroStats: HeroStat[] = [ { headline: 'More databases created in 2025 than in all previous years combined', ... -const gridStats = [ +const gridStats: GridStat[] = [
🧹 Nitpick comments (3)
apps/www/components/Wrapped/Pages/Devs.tsx (1)
133-136: Empty cells calculation doesn't account for responsive column count.The hardcoded
cols = 4only matches the desktop layout. On mobile (grid-cols-2), ifgridStats.lengthchanges to a value not divisible by both 2 and 4, the grid alignment could break.Currently with 8 items this works (8 is divisible by both), but this is fragile for future changes.
Consider either:
- Ensuring
gridStats.lengthis always a multiple of 4 (document this constraint)- Using CSS
grid-auto-rowsor leaving empty cells to CSS auto-fillapps/www/components/Wrapped/Pages/Home.tsx (2)
42-70: AvoidMath.sqrtin overlap checks (compare squared distances).Minor perf/readability:
getDistance()is only used for< minDistance, so you can compare squared distance tominDistance ** 2and skip the sqrt.
197-235: Considerdvhfor mobile viewport correctness.
h-[calc(100vh-64px)](Line 199) can be janky on mobile due to dynamic browser UI. If you need it to be robust on iOS/Android, consider100dvh(or a fallback strategy).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (14)
apps/www/public/images/wrapped/select-2025-003.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-021.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-091.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-099.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-143.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-163.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-190.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-202.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-216.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-229.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-247.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-250.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/select-2025-252.jpgis excluded by!**/*.jpgapps/www/public/images/wrapped/wrapped-og.pngis excluded by!**/*.png
📒 Files selected for processing (4)
apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)apps/www/components/Wrapped/Pages/Home.tsx(1 hunks)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx(1 hunks)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Components should ideally be under 200-300 lines; break down large components with multiple distinct UI sections, complex conditional rendering, or multiple unrelated useState hooks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Extract repeated JSX patterns into reusable components instead of copying similar JSX blocks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-12T05:20:17.409Z
Learnt from: joshenlim
Repo: supabase/supabase PR: 41258
File: apps/studio/pages/project/[ref]/storage/vectors/buckets/[bucketId].tsx:9-9
Timestamp: 2025-12-12T05:20:17.409Z
Learning: In apps/studio/**/*.{ts,tsx}, use named import for DefaultLayout: `import { DefaultLayout } from 'components/layouts/DefaultLayout'` instead of default import. This is the new practice being adopted across the studio app.
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
🧬 Code graph analysis (3)
apps/www/components/Wrapped/Pages/Home.tsx (2)
blocks/vue/registry/default/lib/utils.ts (1)
cn(5-7)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/Pages/Devs.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/Wrapped/AnimatedCounter.tsx (1)
AnimatedCounter(12-87)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (3)
apps/www/types/launch-week-6.ts (1)
Announcement(8-13)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)blocks/vue/registry/default/lib/utils.ts (1)
cn(5-7)
⏰ 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). (3)
- GitHub Check: typecheck
- GitHub Check: test (1)
- GitHub Check: test
🔇 Additional comments (10)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (5)
1-14: LGTM!Imports and type definitions are appropriate. The local
Announcementtype is simpler than the one intypes/launch-week-6.tsand fits this component's specific needs.
16-253: Verify the intentional gap in months.The
monthsarray jumps from May 2025 to August 2025, skipping June and July. If this is intentional (no significant announcements during those months), consider adding a brief comment to clarify. If not, the missing months should be added.
255-289: LGTM!The
MonthSectioncomponent is well-structured with proper security attributes (rel="noopener noreferrer") for external links and appropriate accessibility (visible link text). The use ofannouncement.titleas key is acceptable given titles are unique within each month.
320-334: Dual rendering approach is acceptable but consider the trade-off.The mobile and desktop layouts are rendered separately with CSS visibility toggling. This doubles the DOM nodes (20
MonthSectioninstances instead of 10) but keeps the logic simple. For this static content page with limited items, the trade-off is reasonable. If the list grows significantly, consider using a media query hook to conditionally render only one layout.
294-314: LGTM!The hero section correctly configures
AnimatedGridBackgroundwith valid cell indices for both mobile (5×2=10 cells) and desktop (5×3=15 cells) layouts. The "LaunchWeekYear" visual treatment effectively communicates the year-in-review theme.apps/www/components/Wrapped/Pages/Devs.tsx (5)
1-5: LGTM!The
'use client'directive is appropriate for this component using animated counters and grid backgrounds. Imports are clean.
75-102: LGTM!The hero section correctly integrates
AnimatedGridBackgroundwith valid tile configurations for both mobile and desktop layouts. The responsive height classes and content positioning are well-structured.
104-109: LGTM!Clean intro section with appropriate responsive spacing.
111-128: LGTM!Hero stats are correctly mapped to
AnimatedCountercomponents with appropriate props. Usingstat.headlineas the key is acceptable given the unique headlines.
140-176: LGTM on grid item rendering logic.The conditional rendering between
suffixitems (static formatted values) and animated counters is well-handled. The responsive border classes correctly manage the 2-column mobile and 4-column desktop layouts.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (2)
1-14: Prefer<a>(orprefetch={false}) for external destinations instead ofnext/link.
Allhrefs here are external, andLink’s semantics/optimizations are intended for internal routing; this can also produce warnings depending on Next/router/version.-import Link from 'next/link' import { cn } from 'ui' import { AnimatedGridBackground } from '../AnimatedGridBackground'- <Link - href={announcement.url} + <a + href={announcement.url} target="_blank" rel="noopener noreferrer" className="group flex items-center gap-2 text-sm text-foreground-light hover:text-foreground transition-colors" > <span className="text-foreground-muted group-hover:text-foreground transition-colors"> → </span> {announcement.title} - </Link> + </a>If you want to keep
Link, consider explicitly addingprefetch={false}for external URLs (verify against the repo’s Next.js version).
5-15: Avoid type drift: reuse the sharedAnnouncementtype (orPick) if possible.
There’s already anAnnouncementtype inapps/www/types/launch-week-6.ts(includes extra fields); duplicating locally risks divergence.apps/www/components/Wrapped/Pages/Home.tsx (1)
229-233: Consider making the year dynamic.The hardcoded "2025" could be replaced with
new Date().getFullYear()to keep the content current without manual updates.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/www/components/Wrapped/Pages/CustomerStories.tsx(1 hunks)apps/www/components/Wrapped/Pages/Devs.tsx(1 hunks)apps/www/components/Wrapped/Pages/Home.tsx(1 hunks)apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx(1 hunks)apps/www/components/Wrapped/Pages/SupabaseSelect.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/www/components/Wrapped/Pages/CustomerStories.tsx
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
📚 Learning: 2025-12-11T16:46:03.665Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/e2e-testing.mdc:0-0
Timestamp: 2025-12-11T16:46:03.665Z
Learning: Applies to e2e/studio/**/*.{ts,spec.ts} : Use `page.waitForTimeout()` sparingly and only for non-API waits like client-side debounce or clipboard API operations
Applied to files:
apps/www/components/Wrapped/Pages/Home.tsx
📚 Learning: 2025-12-11T16:46:03.665Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/e2e-testing.mdc:0-0
Timestamp: 2025-12-11T16:46:03.665Z
Learning: Applies to e2e/studio/**/*.{ts,spec.ts} : Prefer Playwright's built-in auto-waiting for elements instead of manual arbitrary timeouts with `page.waitForTimeout()`
Applied to files:
apps/www/components/Wrapped/Pages/Home.tsx
📚 Learning: 2025-12-11T16:46:03.665Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/e2e-testing.mdc:0-0
Timestamp: 2025-12-11T16:46:03.665Z
Learning: Applies to e2e/studio/**/*.{ts,spec.ts} : Use explicit timeouts for slow operations in assertions when needed (e.g., timeout: 50000 for long-running operations)
Applied to files:
apps/www/components/Wrapped/Pages/Home.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Use MultiSelector component from ui-patterns/multi-select for multi-select form fields
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : Use related layout components (e.g., AuthLayout) for pages within existing sections
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for pages with multiple sections
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Add table actions to the right of ScaffoldSectionTitle if table is main content of a page section without search or filters
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Use sheets when revealing complicated forms or information relating to an object where context switching to a new page would be disruptive
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T17:04:40.002Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/**/*.{ts,tsx} : Use Select_Shadcn_ with SelectTrigger_Shadcn_, SelectContent_Shadcn_, and SelectItem_Shadcn_ for select form fields
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Extract repeated JSX patterns into reusable components instead of copying similar JSX blocks
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsxapps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Avoid inline arrow functions for expensive operations; use useCallback to maintain stable function references
Applied to files:
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
📚 Learning: 2025-12-11T16:46:18.690Z
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-best-practices.mdc:0-0
Timestamp: 2025-12-11T16:46:18.690Z
Learning: Applies to apps/studio/**/*.tsx : Components should ideally be under 200-300 lines; break down large components with multiple distinct UI sections, complex conditional rendering, or multiple unrelated useState hooks
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
📚 Learning: 2025-12-12T05:20:17.409Z
Learnt from: joshenlim
Repo: supabase/supabase PR: 41258
File: apps/studio/pages/project/[ref]/storage/vectors/buckets/[bucketId].tsx:9-9
Timestamp: 2025-12-12T05:20:17.409Z
Learning: In apps/studio/**/*.{ts,tsx}, use named import for DefaultLayout: `import { DefaultLayout } from 'components/layouts/DefaultLayout'` instead of default import. This is the new practice being adopted across the studio app.
Applied to files:
apps/www/components/Wrapped/Pages/Devs.tsx
🧬 Code graph analysis (4)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (3)
apps/www/types/launch-week-6.ts (1)
Announcement(8-13)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)blocks/vue/registry/default/lib/utils.ts (1)
cn(5-7)
apps/www/components/Wrapped/Pages/Home.tsx (2)
blocks/vue/registry/default/lib/utils.ts (1)
cn(5-7)apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)
apps/www/components/Wrapped/Pages/Devs.tsx (2)
apps/www/components/Wrapped/AnimatedGridBackground.tsx (1)
AnimatedGridBackground(30-232)apps/www/components/Wrapped/AnimatedCounter.tsx (1)
AnimatedCounter(12-87)
⏰ 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). (3)
- GitHub Check: typecheck
- GitHub Check: test (1)
- GitHub Check: test
🔇 Additional comments (6)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
320-359: Desktop reorder + last-row border logic looks robust.
ThelastRowcomputation andMath.floor(index / 2) === lastRowcondition should behave correctly for odd/even counts.apps/www/components/Wrapped/Pages/Home.tsx (2)
101-161: Excellent fixes—timer cleanup and types are now correct.The previous issues around timer cleanup and type safety have been properly resolved. The
lifetimeTimersRefnow tracks all lifetime timeouts and clears them on unmount, and the browser-safeReturnType<typeof setTimeout>type is used throughout.
1-236: LGTM—clean implementation with good separation of concerns.The component is well-structured with clear helper functions, proper animation lifecycle management, and responsive design. The integration with
AnimatedGridBackgroundfollows the expected API, and the floating bubble logic handles positioning, stat selection, and lifecycle correctly.apps/www/components/Wrapped/Pages/Devs.tsx (3)
1-40: LGTM! Clean type definitions.The 'use client' directive is correctly placed, imports are appropriate, and both
HeroStatandGridStattypes are well-defined with proper TypeScript typing. The optionalsuffixandprefixfields onGridStatprovide good flexibility.
13-88: Well-structured data arrays.Both
heroStatsandgridStatsarrays are properly typed and contain reasonable metrics. The varyingincrementandintervalMsvalues create dynamic animation effects where appropriate, whileincrement: 0correctly marks static values.
90-143: Hero section and stats rendering work well.The hero section with
AnimatedGridBackgroundis properly configured with responsive rows, and the heading effectively uses brand color for emphasis. The hero stats mapping correctly passes all required props toAnimatedCounter.
| {(() => { | ||
| const cols = 4 | ||
| const remainder = gridStats.length % cols | ||
| const emptyCells = remainder === 0 ? 0 : cols - remainder |
There was a problem hiding this comment.
Hardcoded column count breaks responsive layout if data changes.
The cols = 4 constant doesn't account for the mobile 2-column grid (grid-cols-2 lg:grid-cols-4). Currently works because 8 items is divisible by both 2 and 4, but if gridStats grows to 9 or 10 items, empty cells will be incorrect on mobile.
Consider calculating empty cells responsively:
{(() => {
// Detect column count based on screen size
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth < 1024)
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
const cols = isMobile ? 2 : 4
const remainder = gridStats.length % cols
const emptyCells = remainder === 0 ? 0 : cols - remainder
// ... rest of logic
})()}Or keep data at a multiple of 4 items (current approach) and add a comment documenting the constraint.
🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/Devs.tsx around lines 148-151, the code
hardcodes cols = 4 which breaks the responsive grid (mobile uses 2 columns);
replace the constant with a responsive calculation: derive the column count at
runtime (e.g., with a useState boolean like isMobile and a useEffect that sets
it based on window.innerWidth < 1024 and listens for resize, cleaning up the
listener) then set cols = isMobile ? 2 : 4 and recompute remainder/emptyCells
from that value; alternatively, if you prefer not to add JS, document the
requirement that gridStats length must be a multiple of 4 and keep the current
approach.
| {Array.from({ length: emptyCells }).map((_, i) => ( | ||
| <div | ||
| key={`empty-${i}`} | ||
| className="px-6 py-8 border-r border-b border-muted last:border-r-0" | ||
| /> | ||
| ))} |
There was a problem hiding this comment.
Empty cells have inconsistent padding and border logic.
Empty cells use px-6 py-8 while stat cells use p-8, creating different horizontal padding (24px vs 32px). The border logic also differs: last:border-r-0 vs the complex nth-child patterns used for stat cells.
Currently not visible (0 empty cells with 8 items), but will cause visual misalignment if data changes. Apply this diff to match stat cell styling:
{Array.from({ length: emptyCells }).map((_, i) => (
<div
key={`empty-${i}`}
- className="px-6 py-8 border-r border-b border-muted last:border-r-0"
+ className="p-8 border-r border-b border-muted [&:nth-child(2n)]:border-r-0 lg:[&:nth-child(2n)]:border-r [&:nth-child(4n)]:border-r-0"
/>
))}🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/Devs.tsx around lines 192–197, the empty
placeholder cells use different padding and simpler border rules than the stat
cells; change the empty cell className to use the same p-8 padding and the
identical border/nth-child responsive classes used by the stat cells (replace
px-6 py-8 and last:border-r-0 with the same border-r border-b border-muted and
nth-child-based classes used on the stat cells) so padding and border logic
match exactly; keep the same mapping logic and key generation.
| function MonthSection({ month }: { month: Month }) { | ||
| return ( | ||
| <div> | ||
| <div className="px-6 lg:px-8 py-2.5 md:py-4 flex flex-wrap items-center gap-1 [&>*]:whitespace-nowrap [&>*]:mr-2"> | ||
| <span className="text-base font-medium">{month.name}</span> | ||
| {month.isLaunchWeek && ( | ||
| <span className="text-xs bg-brand/10 text-brand-link dark:text-brand px-2 py-0.5 rounded-full"> | ||
| Launch Week | ||
| </span> | ||
| )} | ||
| <span className="text-sm text-foreground-muted"> | ||
| {month.announcements.length} announcements | ||
| </span> | ||
| </div> | ||
|
|
||
| <ul className="px-6 lg:px-8 pb-4 space-y-2"> | ||
| {month.announcements.map((announcement) => ( | ||
| <li key={announcement.title}> | ||
| <Link | ||
| href={announcement.url} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="group flex items-center gap-2 text-sm text-foreground-light hover:text-foreground transition-colors" | ||
| > | ||
| <span className="text-foreground-muted group-hover:text-foreground transition-colors"> | ||
| → | ||
| </span> | ||
| {announcement.title} | ||
| </Link> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Fix React list key + pluralization.
key={announcement.title} can collide, and the label renders “1 announcements”.
- <span className="text-sm text-foreground-muted">
- {month.announcements.length} announcements
- </span>
+ <span className="text-sm text-foreground-muted">
+ {month.announcements.length}{' '}
+ {month.announcements.length === 1 ? 'announcement' : 'announcements'}
+ </span>- {month.announcements.map((announcement) => (
- <li key={announcement.title}>
+ {month.announcements.map((announcement) => (
+ <li key={announcement.url}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function MonthSection({ month }: { month: Month }) { | |
| return ( | |
| <div> | |
| <div className="px-6 lg:px-8 py-2.5 md:py-4 flex flex-wrap items-center gap-1 [&>*]:whitespace-nowrap [&>*]:mr-2"> | |
| <span className="text-base font-medium">{month.name}</span> | |
| {month.isLaunchWeek && ( | |
| <span className="text-xs bg-brand/10 text-brand-link dark:text-brand px-2 py-0.5 rounded-full"> | |
| Launch Week | |
| </span> | |
| )} | |
| <span className="text-sm text-foreground-muted"> | |
| {month.announcements.length} announcements | |
| </span> | |
| </div> | |
| <ul className="px-6 lg:px-8 pb-4 space-y-2"> | |
| {month.announcements.map((announcement) => ( | |
| <li key={announcement.title}> | |
| <Link | |
| href={announcement.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="group flex items-center gap-2 text-sm text-foreground-light hover:text-foreground transition-colors" | |
| > | |
| <span className="text-foreground-muted group-hover:text-foreground transition-colors"> | |
| → | |
| </span> | |
| {announcement.title} | |
| </Link> | |
| </li> | |
| ))} | |
| </ul> | |
| </div> | |
| ) | |
| } | |
| function MonthSection({ month }: { month: Month }) { | |
| return ( | |
| <div> | |
| <div className="px-6 lg:px-8 py-2.5 md:py-4 flex flex-wrap items-center gap-1 [&>*]:whitespace-nowrap [&>*]:mr-2"> | |
| <span className="text-base font-medium">{month.name}</span> | |
| {month.isLaunchWeek && ( | |
| <span className="text-xs bg-brand/10 text-brand-link dark:text-brand px-2 py-0.5 rounded-full"> | |
| Launch Week | |
| </span> | |
| )} | |
| <span className="text-sm text-foreground-muted"> | |
| {month.announcements.length}{' '} | |
| {month.announcements.length === 1 ? 'announcement' : 'announcements'} | |
| </span> | |
| </div> | |
| <ul className="px-6 lg:px-8 pb-4 space-y-2"> | |
| {month.announcements.map((announcement) => ( | |
| <li key={announcement.url}> | |
| <Link | |
| href={announcement.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="group flex items-center gap-2 text-sm text-foreground-light hover:text-foreground transition-colors" | |
| > | |
| <span className="text-foreground-muted group-hover:text-foreground transition-colors"> | |
| → | |
| </span> | |
| {announcement.title} | |
| </Link> | |
| </li> | |
| ))} | |
| </ul> | |
| </div> | |
| ) | |
| } |
🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx around lines 255
to 289, the list uses a non-unique key (announcement.title) which can collide
and the count label always uses the plural "announcements". Replace the key with
a stable unique identifier from the announcement (preferably announcement.id or
announcement.url; fall back to a composite like
`${announcement.url}-${announcement.title}`) and change the count label to
render singular vs plural (e.g., use a conditional: when
month.announcements.length === 1 show "1 announcement" else show "N
announcements").
| fill | ||
| className="object-cover" | ||
| priority={priority} | ||
| sizes="(max-width: 1600px) 100vw, 20vw" |
There was a problem hiding this comment.
Optimize the sizes attribute for better image loading.
The current sizes value doesn't match the actual responsive layout. On desktop, slots span 2-3 columns in a 5-column grid within a max-width 60rem container, not 20vw. This mismatch causes Next.js to select suboptimal image sizes.
Consider updating to reflect the actual layout:
- sizes="(max-width: 1600px) 100vw, 20vw"
+ sizes="(max-width: 1024px) 100vw, (max-width: 1600px) 36vw, 576px"This accounts for:
- Mobile: full width (100vw)
- Tablet-Desktop: ~60% of viewport for 3-column slots (36vw)
- Large screens: max container width (960px) × 60% ≈ 576px
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sizes="(max-width: 1600px) 100vw, 20vw" | |
| sizes="(max-width: 1024px) 100vw, (max-width: 1600px) 36vw, 576px" |
🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/SupabaseSelect.tsx around line 61, the
sizes attribute is incorrect for the actual responsive layout; replace it with a
sizes string that reflects mobile as 100vw, tablet/desktop as roughly 36vw for
3-column slots, and a fixed large-screen fallback of ~576px (e.g. "(max-width:
640px) 100vw, (max-width: 1200px) 36vw, 576px") so Next.js can pick appropriate
image widths.
| currentImage={allImages[slotImages[index]]} | ||
| span={slot.span} | ||
| imageKey={imageKeys[index]} | ||
| priority={index === 0 && isInView} |
There was a problem hiding this comment.
Simplify the priority prop to be static.
The priority prop is conditionally set based on isInView, but Next.js Image priority should be static at initial render to enable preloading. By the time isInView becomes true, the image has already started loading without priority, making the conditional ineffective.
Apply this diff:
- priority={index === 0 && isInView}
+ priority={index === 0}Alternatively, if you want to avoid preloading images that may never be seen, remove the priority prop entirely since the gallery is further down the page.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| priority={index === 0 && isInView} | |
| priority={index === 0} |
🤖 Prompt for AI Agents
In apps/www/components/Wrapped/Pages/SupabaseSelect.tsx around line 213, the
Image component's priority prop is currently conditional on runtime isInView
which defeats Next.js static preload behavior; change it to a static expression
(e.g., priority={index === 0}) so the first image is marked for preload on
initial render, or remove the priority prop entirely if you prefer not to
preload any gallery images.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
269-276: Unaddressed: Fix pluralization and use unique keys.The issues flagged in the previous review remain:
- Line 270: Renders "1 announcements" when a month has a single announcement.
- Line 276: Using
announcement.titleas key risks collisions;announcement.urlis guaranteed unique.<span className="text-sm text-foreground-muted"> - {month.announcements.length} announcements + {month.announcements.length}{' '} + {month.announcements.length === 1 ? 'announcement' : 'announcements'} </span>- {month.announcements.map((announcement) => ( - <li key={announcement.title}> + {month.announcements.map((announcement) => ( + <li key={announcement.url}>
🧹 Nitpick comments (1)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
277-288: Consider improving accessibility for external links.The arrow is decorative and may be announced by screen readers. Additionally, users relying on assistive technology won't know these links open in a new tab.
<Link href={announcement.url} target="_blank" rel="noopener noreferrer" className="group flex items-center gap-2 text-sm text-foreground-light hover:text-foreground transition-colors" > - <span className="text-foreground-muted group-hover:text-foreground transition-colors"> + <span aria-hidden="true" className="text-foreground-muted group-hover:text-foreground transition-colors"> → </span> {announcement.title} + <span className="sr-only">(opens in new tab)</span> </Link>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/components/interfaces/**/*.{ts,tsx} : Create a new component in apps/studio/components/interfaces for the contents of each new page
Learnt from: CR
Repo: supabase/supabase PR: 0
File: .cursor/rules/studio-ui.mdc:0-0
Timestamp: 2025-12-11T17:04:40.002Z
Learning: Applies to apps/studio/pages/**/*.{ts,tsx} : When creating a new page, use the PageLayout component with appropriate props (title, subtitle, icon, breadcrumbs, primaryActions, secondaryActions, navigationItems, className, size, isCompact)
⏰ 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). (3)
- GitHub Check: test
- GitHub Check: test (1)
- GitHub Check: typecheck
🔇 Additional comments (1)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)
340-363: LGTM on the reordering and border logic.The previous feedback has been addressed:
originalIndexis removed, and the last-row border calculation now correctly usesMath.floor(index / 2) === lastRow, which handles odd month counts properly.
I have read the CONTRIBUTING.md file.
YES
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.