Skip to content

Supabase Wrapped 2025#41091

Merged
stylessh merged 64 commits into
masterfrom
alan/supabase-wrapped
Dec 16, 2025
Merged

Supabase Wrapped 2025#41091
stylessh merged 64 commits into
masterfrom
alan/supabase-wrapped

Conversation

@stylessh

@stylessh stylessh commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

I have read the CONTRIBUTING.md file.

YES

Summary by CodeRabbit

Release Notes

  • New Features
    • Launched Supabase Wrapped 2025 experience showcasing year-end statistics, developer metrics, and platform insights
    • Added animated grid backgrounds and floating visual elements for enhanced visual engagement
    • Featured customer testimonials and highlighted product announcements from throughout 2025
    • Included smooth animations and interactive transitions for improved user experience

✏️ Tip: You can customize this high-level summary in your review settings.

@stylessh stylessh requested a review from a team as a code owner December 4, 2025 21:23
@vercel

vercel Bot commented Dec 4, 2025

Copy link
Copy Markdown

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

Project Deployment Review Updated (UTC)
zone-www-dot-com Ready Ready Preview, Comment Dec 16, 2025 2:39pm
7 Skipped Deployments
Project Deployment Review Updated (UTC)
studio Ignored Ignored Dec 16, 2025 2:39pm
cms Skipped Skipped Dec 16, 2025 2:39pm
design-system Skipped Skipped Dec 16, 2025 2:39pm
docs Skipped Skipped Dec 16, 2025 2:39pm
studio-self-hosted Skipped Skipped Dec 16, 2025 2:39pm
studio-staging Skipped Skipped Dec 16, 2025 2:39pm
ui-library Skipped Skipped Dec 16, 2025 2:39pm

@supabase

supabase Bot commented Dec 4, 2025

Copy link
Copy Markdown

This pull request has been ignored for the connected project xguihxuzqibwxjnimxev because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@github-actions

github-actions Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results

passed  67 passed
skipped  4 skipped

Details

stats  71 tests across 12 suites
duration  7 minutes, 45 seconds
commit  8ace266

Skipped tests

Features › sql-editor.spec.ts › SQL Editor › snippet favourite works as expected
Features › sql-editor.spec.ts › SQL Editor › share with team works as expected
Features › sql-editor.spec.ts › SQL Editor › folders works as expected
Features › sql-editor.spec.ts › SQL Editor › other SQL snippets actions work as expected

@blacksmith-sh

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Dec 11, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Introduces 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

Cohort / File(s) Summary
Wrapped page entry
apps/www/app/wrapped/page.tsx
Exports page metadata with Open Graph and Twitter card details for Supabase Wrapped 2025 and renders the WrappedClient component
Wrapped page client composition
apps/www/app/wrapped/WrappedClient.tsx
Client component composing multiple page sections (Home, Intro, YearOfAI, Devs, ProductAnnouncements, SupabaseSelect, CustomerStories) within a DefaultLayout and ProductsCta
Animated utility components
apps/www/components/Wrapped/AnimatedCounter.tsx, AnimatedGridBackground.tsx, Visuals.tsx
AnimatedCounter animates numeric values on visibility using IntersectionObserver and requestAnimationFrame; AnimatedGridBackground renders responsive grid with moving tiles and staggered entry animations; Visuals exports Stripes, Dots, and Android presentational components
Logo components
apps/www/components/Wrapped/Logos.tsx
Exports multiple SVG logo components (PhoenixEnergy, Rally, Soshi, KayhanSpace, Juniver, Deriv, Resend, and others) for use in testimonials
Page sections
apps/www/components/Wrapped/Pages/Home.tsx
Hero section with AnimatedGridBackground and floating statistic bubbles with lifecycle management and non-overlapping positioning
Intro.tsx
YearOfAI.tsx
Devs.tsx
ProductAnnouncements.tsx
SupabaseSelect.tsx
CustomerStories.tsx
Configuration and dependencies
apps/www/package.json
Added @bsmnt/scrollytelling ^0.3.3 and gsap ^3.13.0 dependencies
apps/www/tailwind.config.js
Minor UI update
apps/www/components/Sections/ProductsCta2.tsx
Added w-max utility class to heading element

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • AnimatedCounter: Review IntersectionObserver setup, requestAnimationFrame loop, number formatting logic (compact vs. toLocaleString), and cleanup on unmount
  • AnimatedGridBackground: Verify tile positioning math, movement logic (random selection and repositioning), and Framer Motion animation configuration with staggered delays
  • Home component: Examine FloatingStatBubble positioning algorithm (distance calculation, non-overlapping generation), state management for bubble lifecycle, and timeout cleanup
  • Intro component: Review FloatingBoxes lifecycle, spawning intervals, drift vectors, and scrollytelling integration with Scrollytelling.Animation
  • SupabaseSelect: Validate rotation timer logic per slot, in-view detection, and state synchronization to prevent duplicate image display
  • Integration of new libraries: Confirm correct usage of @bsmnt/scrollytelling and gsap with proper cleanup and memory management

Possibly related PRs

  • supabase/supabase#41091: Directly related PR adding the same Wrapped feature set with identical page components, animated utilities, and layout structure

Suggested reviewers

  • dnywh
  • kemaldotearth
  • fsansalvadore

Poem

🐰 Hop along, dear reader, to wrapped delights,
Statistics dance in animated flights,
Grids that glide and counters that spin,
A year of Supabase wrapped up within,
With floating bubbles and tiles that roam,
Welcome to Wrapped—a creative home!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. While the author confirmed reading CONTRIBUTING.md, the description lacks required sections: type of change, current behavior/issues, new behavior, and additional context. Complete the PR description by filling out all template sections: specify change type (feature), link relevant issues, describe the new Wrapped 2025 experience, and add visual context or screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.98% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Supabase Wrapped 2025' directly corresponds to the main feature added: a new Wrapped experience page and components for Supabase Wrapped 2025.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e42b3df and 8ace266.

📒 Files selected for processing (7)
  • 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)

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (11)
apps/www/components/Wrapped/AnimatedCounter.tsx (3)

18-22: Refs not synchronized when props change.

If value or increment props change after mount, countRef.current retains 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 requestAnimationFrame loop 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 existing AnimatedCounter in packages/ui.

There's an existing AnimatedCounter component in packages/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 IncrementingCounter or LiveCounter to 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 moveRandomTile depends on tilePositions, the interval is cleared and recreated after every tile movement. This works but is inefficient. Consider using a ref to access tilePositions to keep moveRandomTile stable:

+ 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.

Android is imported but never used in this component.

-import { Android } from '../Visuals'
apps/www/components/Wrapped/Pages/CustomerStories.tsx (1)

75-78: Consider using the cols variable consistently.

The cols variable is declared but only used for empty cell calculation. The grid layout hardcodes lg:grid-cols-2. Consider either removing the variable and hardcoding 2 in 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 - remainder
apps/www/components/Wrapped/Pages/AnnouncementsTimeline.tsx (2)

533-543: Consider using index instead of title for hover comparison.

Line 539 compares hoveredItem?.title === item.title to 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 controls opacity and filter. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8e3ce67 and ef9e07a.

⛔ Files ignored due to path filters (14)
  • apps/www/public/images/wrapped/rachelzphotographyllc-21.jpeg is excluded by !**/*.jpeg
  • apps/www/public/images/wrapped/rachelzphotographyllc-91.jpeg is excluded by !**/*.jpeg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-143.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-163.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-190.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-202.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-216.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-229.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-247.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-250.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-252.JPG is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-3.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/rachelzphotographyllc1-99.jpg is excluded by !**/*.jpg
  • pnpm-lock.yaml is 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.tsx
  • apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
  • apps/www/components/Wrapped/Pages/Intro.tsx
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.tsx
  • apps/www/components/Wrapped/Pages/CustomerStories.tsx
  • apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
  • apps/www/components/Wrapped/Pages/Intro.tsx
  • apps/www/components/Wrapped/Pages/Home.tsx
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.tsx
  • apps/www/components/Wrapped/Pages/CustomerStories.tsx
  • apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx
  • apps/www/components/Wrapped/Pages/Intro.tsx
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/www/tailwind.config.js
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.tsx
  • apps/www/tailwind.config.js
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.js
  • 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/pages/**/*.{ts,tsx} : Next.js pages should be created in apps/studio/pages directory

Applied to files:

  • apps/www/tailwind.config.js
  • apps/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.js
  • apps/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.js
  • 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 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.js
  • apps/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 the suffix pattern to bypass AnimatedCounter, 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 Stripes component uses clean CSS-only gradients, and the Android SVG component correctly forwards props using ComponentProps<'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 SurveyStatCard with label and percent props 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" with rel="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/blog which 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" with rel="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 viewBox attributes for scalability
apps/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.length correctly 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 null when 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.

Comment thread apps/www/app/wrapped/page.tsx Outdated
Comment thread apps/www/components/Wrapped/AnimatedGridBackground.tsx
Comment on lines +298 to +340
{
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',
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# 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 -20

Repository: 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.

Comment thread apps/www/components/Wrapped/Pages/CustomerStories.tsx Outdated
Comment thread apps/www/components/Wrapped/Pages/Home.tsx
Comment thread apps/www/components/Wrapped/Pages/Intro.tsx Outdated
Comment thread apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
Comment thread apps/www/package.json
Comment thread apps/www/package.json Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@coveralls

coveralls commented Dec 11, 2025

Copy link
Copy Markdown

Coverage Status

coverage: 68.859% (+0.3%) from 68.541%
when pulling 8ace266 on alan/supabase-wrapped
into 3a41846 on master.

ivasilov and others added 8 commits December 11, 2025 09:41
* 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/www/app/wrapped/page.tsx (1)

11-26: Rename the ProductsCta2 import to match the file/component for readability.
Line 11 imports ProductsCta2 but aliases it as ProductsCta, which is easy to misread during maintenance (especially alongside any existing ProductsCta).

-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 allocating Intl.NumberFormat inside 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

📥 Commits

Reviewing files that changed from the base of the PR and between fa22003 and bdbad6c.

📒 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.tsx
  • 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/**/*.{ts,tsx} : Use ScaffoldSection, ScaffoldSectionTitle, and ScaffoldSectionDescription for pages with multiple sections

Applied to files:

  • apps/www/components/Sections/ProductsCta2.tsx
  • 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} : 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.tsx
  • 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/app/wrapped/page.tsx
  • 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/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.tsx
  • 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-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-max is fine here; just ensure no small-screen overflow if copy changes.

This is a safe visual tweak with current heading text, but w-max can cause horizontal overflow if marketing copy becomes longer or localized. Consider max-w-full (or removing w-max) if overflow is observed in responsive previews.

apps/www/components/Wrapped/Pages/Devs.tsx (1)

78-92: Verify AnimatedGridBackground handles rows={{ 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.

Comment thread apps/www/components/Wrapped/Pages/Devs.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 (matches PRODUCT_SHORTNAMES.FUNCTIONS), though consider using the enum constant for consistency with other product pages like edge-functions.tsx.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdbad6c and aa096b0.

⛔ Files ignored due to path filters (1)
  • apps/www/public/images/wrapped/wrapped-og.png is 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (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 for gridStats.

Line 152 uses (stat as any).prefix which 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 unused originalIndex and make last-row border logic robust for odd month counts

This issue was previously identified: originalIndex is computed but never used, and the border logic index >= reordered.length - 2 assumes 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 setInterval gated 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.NumberFormat on every call adds minor overhead. Since compact is 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 extracting cols to match the grid definition.

The cols constant is defined inside the IIFE (line 134) but the grid uses lg: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 = 4

Or extract the calculation outside the render. This is a minor clarity improvement.

apps/www/components/Wrapped/Pages/SupabaseSelect.tsx (1)

53-58: Add sizes prop to optimize image loading.

When using fill prop with next/image, adding sizes helps 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

📥 Commits

Reviewing files that changed from the base of the PR and between aa096b0 and 03295b2.

📒 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.tsx
  • apps/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.tsx
  • apps/www/components/Wrapped/Pages/CustomerStories.tsx
  • apps/www/components/Wrapped/Pages/SupabaseSelect.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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 AnimatedCounter in packages/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 AnimatedCounter with appropriate props. The varying intervalMs values 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 isCancelled flag 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 lifetimeTimersRef pattern 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:

  • getDistance correctly computes Euclidean distance
  • generateNonOverlappingPosition has a bounded retry limit (20 attempts) preventing infinite loops
  • generateRandomBubble gracefully falls back to any stat when all have been used

142-153: LGTM on spawn scheduling.

The recursive setTimeout pattern correctly creates randomized spawn intervals. Clearing intervalId on unmount breaks the chain since each timeout schedules its successor.


197-235: LGTM!

The Home component 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (1)

336-358: The originalIndex issue was fixed, but the fragile border logic remains.

The unused originalIndex from the past review has been removed. However, the index >= reordered.length - 2 heuristic for lg:border-b-0 is 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 for heroStats and gridStats.

The data arrays lack TypeScript type definitions, which leads to the (stat as any).prefix cast 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 = 4 only matches the desktop layout. On mobile (grid-cols-2), if gridStats.length changes 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:

  1. Ensuring gridStats.length is always a multiple of 4 (document this constraint)
  2. Using CSS grid-auto-rows or leaving empty cells to CSS auto-fill
apps/www/components/Wrapped/Pages/Home.tsx (2)

42-70: Avoid Math.sqrt in overlap checks (compare squared distances).

Minor perf/readability: getDistance() is only used for < minDistance, so you can compare squared distance to minDistance ** 2 and skip the sqrt.


197-235: Consider dvh for 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, consider 100dvh (or a fallback strategy).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03295b2 and e7f1cbb.

⛔ Files ignored due to path filters (14)
  • apps/www/public/images/wrapped/select-2025-003.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-021.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-091.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-099.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-143.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-163.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-190.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-202.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-216.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-229.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-247.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-250.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/select-2025-252.jpg is excluded by !**/*.jpg
  • apps/www/public/images/wrapped/wrapped-og.png is 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 Announcement type is simpler than the one in types/launch-week-6.ts and fits this component's specific needs.


16-253: Verify the intentional gap in months.

The months array 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 MonthSection component is well-structured with proper security attributes (rel="noopener noreferrer") for external links and appropriate accessibility (visible link text). The use of announcement.title as 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 MonthSection instances 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 AnimatedGridBackground with valid cell indices for both mobile (5×2=10 cells) and desktop (5×3=15 cells) layouts. The "Launch Week Year" 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 AnimatedGridBackground with 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 AnimatedCounter components with appropriate props. Using stat.headline as the key is acceptable given the unique headlines.


140-176: LGTM on grid item rendering logic.

The conditional rendering between suffix items (static formatted values) and animated counters is well-handled. The responsive border classes correctly manage the 2-column mobile and 4-column desktop layouts.

Comment thread apps/www/components/Wrapped/Pages/Home.tsx

@dnywh dnywh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good! Let’s ship

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
apps/www/components/Wrapped/Pages/ProductAnnouncements.tsx (2)

1-14: Prefer <a> (or prefetch={false}) for external destinations instead of next/link.
All hrefs here are external, and Link’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 adding prefetch={false} for external URLs (verify against the repo’s Next.js version).


5-15: Avoid type drift: reuse the shared Announcement type (or Pick) if possible.
There’s already an Announcement type in apps/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

📥 Commits

Reviewing files that changed from the base of the PR and between e7f1cbb and 1f5938d.

📒 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.tsx
  • 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/SupabaseSelect.tsx
  • 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/**/*.{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.tsx
  • 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 : 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.
The lastRow computation and Math.floor(index / 2) === lastRow condition 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 lifetimeTimersRef now tracks all lifetime timeouts and clears them on unmount, and the browser-safe ReturnType<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 AnimatedGridBackground follows 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 HeroStat and GridStat types are well-defined with proper TypeScript typing. The optional suffix and prefix fields on GridStat provide good flexibility.


13-88: Well-structured data arrays.

Both heroStats and gridStats arrays are properly typed and contain reasonable metrics. The varying increment and intervalMs values create dynamic animation effects where appropriate, while increment: 0 correctly marks static values.


90-143: Hero section and stats rendering work well.

The hero section with AnimatedGridBackground is properly configured with responsive rows, and the heading effectively uses brand color for emphasis. The hero stats mapping correctly passes all required props to AnimatedCounter.

Comment on lines +148 to +151
{(() => {
const cols = 4
const remainder = gridStats.length % cols
const emptyCells = remainder === 0 ? 0 : cols - remainder

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +192 to +197
{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"
/>
))}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +255 to +289
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>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (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:

  1. Line 270: Renders "1 announcements" when a month has a single announcement.
  2. Line 276: Using announcement.title as key risks collisions; announcement.url is 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

📥 Commits

Reviewing files that changed from the base of the PR and between e948d7b and e42b3df.

📒 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: originalIndex is removed, and the last-row border calculation now correctly uses Math.floor(index / 2) === lastRow, which handles odd month counts properly.

@stylessh stylessh enabled auto-merge (squash) December 16, 2025 14:34
@stylessh stylessh merged commit a469504 into master Dec 16, 2025
21 of 22 checks passed
@stylessh stylessh deleted the alan/supabase-wrapped branch December 16, 2025 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.