Skip to content

Comments

Design refresh for marketing pages#980

Merged
elie222 merged 17 commits intoelie222:mainfrom
jshwrnr:feat/design-refresh
Nov 18, 2025
Merged

Design refresh for marketing pages#980
elie222 merged 17 commits intoelie222:mainfrom
jshwrnr:feat/design-refresh

Conversation

@jshwrnr
Copy link
Collaborator

@jshwrnr jshwrnr commented Nov 16, 2025

Summary by CodeRabbit

  • New Features

    • New Banner; richer Hero with video player; configurable Brand scroller and published brand lists; Old pricing lazy-loader added.
  • Style

    • Landing refreshed with card-based FAQ layout, updated section compositions, and refined layout classes.
  • Refactor

    • Several landing sections made props-driven for flexible titles/subtitles; CTA usages replaced with final CTA variant.
  • Removed

    • Legacy hero/faq/layout files removed; FAQs and CTA components reorganized.

Note

Redesigns marketing/landing pages with a new customizable Hero and FAQs, adds Banner and footer variants, refactors BrandScroller and layouts, and updates Pricing lazy import.

  • Landing/Marketing Pages:
    • Hero: New customizable Hero with badge support and HeroVideoPlayer; integrated on / with WordReveal and BrandScroller.
    • FAQs: Rebuilt using new components with added entries (feature requests, open source, refunds, free trial) and semantic dt/dd.
    • CTAs: Replace CTA with FinalCTA across pages.
    • Pricing: PricingLazy now loads components/new-landing/sections/Pricing and no longer forwards props.
  • Components & Layouts:
    • Banner: New components/common/Banner.
    • Layouts: BasicLayout and BlogLayout now use new Header/Footer; Footer supports variant="simple".
    • BrandScroller: Now configurable (brandList, animate), displays userCount, and uses centralized brands.
    • Typography: Paragraph supports dt/dl tags.
    • Sections: OrganizedInbox, PreWrittenDrafts, StartedInMinutes accept title/subtitle props.
  • Utils:
    • Brands: New utils/brands exporting BRANDS_LIST and types.

Written by Cursor Bugbot for commit 571b2da. Configure here.

@vercel
Copy link

vercel bot commented Nov 16, 2025

@jshwrnr is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Nov 16, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Large UI rearrangement: landing/new-landing components were added, removed, or made prop-driven; Brand data moved to a utility; Banner and layout updates introduced; Pricing lazy-load wrappers changed; VSCode format-on-save override removed.

Changes

Cohort / File(s) Summary
Editor config
\.vscode/settings.json
Removed explicit "editor.formatOnSave": false.
Pricing lazy wrappers
apps/web/app/(app)/premium/PricingLazy.tsx, apps/web/app/(app)/premium/OldPricingLazy.tsx
PricingLazy dynamic import path changed to ../../../components/new-landing/sections/Pricing and prop forwarding removed; new OldPricingLazy added that forwards PricingProps and uses dynamic import + Suspense.
Welcome / upgrade pages
apps/web/app/(landing)/welcome-upgrade/page.tsx
Replaced PricingLazy usage with OldPricingLazy.
Landing pages wiring
apps/web/app/(landing)/page.tsx, apps/web/app/(landing)/old-landing/page.tsx, apps/web/app/(landing)/oss-friends/page.tsx
page.tsx now composes richer Hero (with HeroVideoPlayer, BrandScroller, and new sections); older pages swap CTAFinalCTA imports/usage.
Home (landing) components changed/removed
apps/web/app/(landing)/home/Hero.tsx, apps/web/app/(landing)/home/FAQs.tsx, apps/web/app/(landing)/home/CTA.tsx
Hero.tsx API/implementation replaced (new HeroProps, HeroVideoPlayer, badge API); FAQs.tsx updated (new items + card-based layout); CTA.tsx deleted.
New components added
apps/web/components/common/Banner.tsx, apps/web/components/layouts/BlogLayout.tsx, apps/web/components/layouts/BasicLayout.tsx
Added Banner; adjusted BasicLayout/BlogLayout to use shared layout classname and sticky header pattern.
New-landing components removed
apps/web/components/new-landing/common/BasicLayout.tsx, apps/web/components/new-landing/sections/FAQs.tsx, apps/web/components/new-landing/sections/Hero.tsx
Deleted BasicLayout, FAQs, and Hero from new-landing (logic rehomed or removed).
New-landing sections made prop-driven
apps/web/components/new-landing/sections/OrganizedInbox.tsx, .../PreWrittenDrafts.tsx, .../StartedInMinutes.tsx, .../Pricing.tsx
Components now accept { title, subtitle } props (new interfaces) replacing hardcoded strings.
Branding / marquee
apps/web/components/new-landing/BrandScroller.tsx, apps/web/utils/brands.ts
BrandScroller now accepts brandList and animate props; created apps/web/utils/brands.ts exporting Brand types and BRANDS_LIST (default, realtor).
Typography & Footer
apps/web/components/new-landing/common/Typography.tsx, apps/web/components/new-landing/sections/Footer.tsx
Paragraph.as allowed elements expanded (dt, dl); Footer gained optional `variant?: "default"
Marketing submodule
apps/web/app/(marketing)
Submodule commit pointer updated.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant B as Browser
    participant LP as LandingPage (apps/web/app/(landing)/page.tsx)
    participant Hero as Hero (home/Hero.tsx)
    participant Sections as Section components
    participant BrandU as BRANDS_LIST (apps/web/utils/brands.ts)
    participant BS as BrandScroller
    participant Footer as Footer

    B->>LP: GET / (render)
    LP->>Hero: render Hero(title, subtitle, badge, children)
    Hero->>BS: render BrandScroller(brandList?)
    BS->>BrandU: request brandList (default/realtor)
    BrandU-->>BS: return brands array
    Hero->>Sections: render OrganizedInbox / PreWrittenDrafts / StartedInMinutes with props
    LP->>Footer: render Footer(variant="default")
    Footer->>B: return final markup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Files to focus on:
    • apps/web/app/(landing)/home/Hero.tsx — new API, HeroVideoPlayer, analytics/tracking and children composition.
    • apps/web/components/new-landing/BrandScroller.tsx + apps/web/utils/brands.ts — data shapes, image paths, defaults.
    • apps/web/app/(app)/premium/PricingLazy.tsx and OldPricingLazy.tsx — dynamic import targets and prop forwarding differences.
    • Deleted files (new-landing/sections/Hero.tsx, FAQs.tsx, common/BasicLayout.tsx, CTA.tsx) — ensure no stale imports remain.

Possibly related PRs

Poem

🐰
I nudge the Hero, hop the brands in line,
A banner blooms where layout used to dine.
Props tucked neat and FAQs in stacks,
Pricing lazy loads — I hop back for snacks. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Design refresh for marketing pages' directly summarizes the main objective of the PR, which is a comprehensive redesign of marketing and landing pages including Hero, FAQs, layouts, and related components.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 962fb4c and 9b5fd09.

📒 Files selected for processing (3)
  • apps/web/app/(app)/premium/OldPricingLazy.tsx (1 hunks)
  • apps/web/app/(landing)/welcome-upgrade/page.tsx (1 hunks)
  • apps/web/app/(marketing) (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(marketing)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-05T09:49:12.168Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 485
File: apps/web/app/(landing)/login/page.tsx:41-43
Timestamp: 2025-06-05T09:49:12.168Z
Learning: In Next.js App Router, components that use the `useSearchParams` hook require a Suspense boundary to handle the asynchronous nature of search parameter access. The Suspense wrapper is necessary and should not be removed when a component uses useSearchParams.

Applied to files:

  • apps/web/app/(app)/premium/OldPricingLazy.tsx
🧬 Code graph analysis (2)
apps/web/app/(landing)/welcome-upgrade/page.tsx (2)
apps/web/app/(landing)/welcome-upgrade/WelcomeUpgradeNav.tsx (1)
  • WelcomeUpgradeNav (6-21)
apps/web/app/(app)/premium/OldPricingLazy.tsx (1)
  • OldPricingLazy (8-12)
apps/web/app/(app)/premium/OldPricingLazy.tsx (2)
apps/web/app/(app)/premium/Pricing.tsx (1)
  • PricingProps (41-45)
apps/web/components/Loading.tsx (1)
  • Loading (3-9)
⏰ 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: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (3)
apps/web/app/(landing)/welcome-upgrade/page.tsx (2)

4-4: Verify the import path change is intentional.

The import has changed from a landing page PricingLazy to OldPricingLazy from the app premium directory. Ensure this path change aligns with the architectural intent of the refactor.


10-10: Props removal is intentional and poses no functional issue.

The props in question have the following status:

  • showSkipUpgrade: Defined in PricingProps but never actually used in the Pricing component implementation, so its removal has no functional impact.
  • header: Used in the Pricing component with a sensible fallback—if not provided, the component renders a default header. The welcome-upgrade page now uses this default instead of a custom header, which appears to be an intentional design choice.

Since both props are optional and the component functions correctly without them, this change does not break functionality or user experience.

apps/web/app/(app)/premium/OldPricingLazy.tsx (1)

1-12: Implementation follows Next.js best practices.

The lazy loading implementation correctly uses dynamic() with a Suspense boundary and forwards props appropriately. This aligns with Next.js patterns for code splitting and the requirement for Suspense boundaries in the App Router.

However, verify the "Old" naming convention—this suggests a temporary or legacy component. Ensure this naming aligns with the team's architectural plan and that the component's lifecycle is documented (e.g., when it will be deprecated or merged).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
apps/web/components/common/Banner.tsx (1)

13-15: Consider making the heading level configurable.

The title is always rendered as an h2, which may not fit all contexts where this component is used. Consider accepting a heading level prop to maintain proper semantic hierarchy.

For example:

 interface BannerProps {
   title: React.ReactNode;
   children: React.ReactNode;
+  as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
 }

-export function Banner({ title, children }: BannerProps) {
+export function Banner({ title, children, as: Component = "h2" }: BannerProps) {
   return (
     <div className="relative border border-[#E7E7E7A3] rounded-3xl my-10 px-6 py-24 sm:py-32 lg:px-8 overflow-hidden">
       <UnicornScene className="opacity-10" />
       <div className="mx-auto max-w-2xl text-center">
-        <h2 className="font-title text-3xl text-gray-900 sm:text-4xl">
+        <Component className="font-title text-3xl text-gray-900 sm:text-4xl">
           {title}
-        </h2>
+        </Component>
apps/web/components/new-landing/sections/Footer.tsx (1)

132-182: Footer variant handling is solid; consider loosening className

The new variant prop and conditional rendering of UnicornScene / FooterLineLogo are straightforward and keep the default look unchanged.

You might make className optional for ergonomics, since it’s already safely passed through cn:

-interface FooterProps {
-  className: string;
-  variant?: "default" | "simple";
-}
+interface FooterProps {
+  className?: string;
+  variant?: "default" | "simple";
+}
apps/web/app/(landing)/home/FAQs.tsx (1)

1-97: FAQ card layout and content look good; optional typing tweak

The refactor to a Card-based FAQ grid with Section/Paragraph composition reads well and keeps semantics via dt/dd. The new entries (feature requests, refunds via NEXT_PUBLIC_SUPPORT_EMAIL, free trial) are wired correctly.

If you want slightly clearer types for future edits, you could make the FAQ shape explicit:

-import { Anchor } from "@/components/new-landing/common/Anchor";
+import { Anchor } from "@/components/new-landing/common/Anchor";
+import type { ReactNode } from "react";
@@
-const faqs = [
+const faqs: { question: string; answer: ReactNode }[] = [

Not required, but it makes the mixed string/JSX answer field more self-documenting.

apps/web/utils/brands.ts (1)

1-79: Type‑safe brand registry looks solid

Nice use of as const with satisfies to keep the brand data and lists type-safe (BrandKey / BrandListKey are handy).

If height is intended to hold arbitrary Tailwind classes (e.g. translations, responsive heights), you might consider a more general name like className or logoClassName before this spreads across the codebase, but that’s strictly a naming nit.

apps/web/components/new-landing/common/Typography.tsx (1)

77-77: Consider the semantic meaning of "dl" for the Paragraph component.

The as prop now allows rendering as a definition list (dl), which is semantically a container element rather than a paragraph-like element. While this provides flexibility, rendering a "Paragraph" component as a dl may be semantically confusing since dl typically contains dt and dd children rather than text content.

If you need definition list styling, consider whether a separate component or wrapper might be more semantically appropriate.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a48330 and 4a3dbf6.

📒 Files selected for processing (22)
  • .vscode/settings.json (0 hunks)
  • apps/web/app/(app)/premium/PricingLazy.tsx (1 hunks)
  • apps/web/app/(landing)/home/CTA.tsx (0 hunks)
  • apps/web/app/(landing)/home/FAQs.tsx (1 hunks)
  • apps/web/app/(landing)/home/Hero.tsx (1 hunks)
  • apps/web/app/(landing)/old-landing/page.tsx (2 hunks)
  • apps/web/app/(landing)/oss-friends/page.tsx (2 hunks)
  • apps/web/app/(landing)/page.tsx (1 hunks)
  • apps/web/app/(marketing) (1 hunks)
  • apps/web/components/common/Banner.tsx (1 hunks)
  • apps/web/components/layouts/BasicLayout.tsx (1 hunks)
  • apps/web/components/layouts/BlogLayout.tsx (1 hunks)
  • apps/web/components/new-landing/BrandScroller.tsx (1 hunks)
  • apps/web/components/new-landing/common/BasicLayout.tsx (0 hunks)
  • apps/web/components/new-landing/common/Typography.tsx (1 hunks)
  • apps/web/components/new-landing/sections/FAQs.tsx (0 hunks)
  • apps/web/components/new-landing/sections/Footer.tsx (2 hunks)
  • apps/web/components/new-landing/sections/Hero.tsx (0 hunks)
  • apps/web/components/new-landing/sections/OrganizedInbox.tsx (1 hunks)
  • apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (1 hunks)
  • apps/web/components/new-landing/sections/StartedInMinutes.tsx (1 hunks)
  • apps/web/utils/brands.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • apps/web/components/new-landing/common/BasicLayout.tsx
  • apps/web/components/new-landing/sections/Hero.tsx
  • apps/web/components/new-landing/sections/FAQs.tsx
  • .vscode/settings.json
  • apps/web/app/(landing)/home/CTA.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-05T09:49:12.168Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 485
File: apps/web/app/(landing)/login/page.tsx:41-43
Timestamp: 2025-06-05T09:49:12.168Z
Learning: In Next.js App Router, components that use the `useSearchParams` hook require a Suspense boundary to handle the asynchronous nature of search parameter access. The Suspense wrapper is necessary and should not be removed when a component uses useSearchParams.

Applied to files:

  • apps/web/app/(app)/premium/PricingLazy.tsx
🧬 Code graph analysis (13)
apps/web/components/new-landing/sections/StartedInMinutes.tsx (2)
apps/web/components/new-landing/common/Section.tsx (1)
  • Section (9-15)
apps/web/components/new-landing/common/Typography.tsx (2)
  • SectionHeading (34-40)
  • SectionSubtitle (46-52)
apps/web/components/new-landing/sections/OrganizedInbox.tsx (2)
apps/web/components/new-landing/common/Section.tsx (1)
  • Section (9-15)
apps/web/components/new-landing/common/Typography.tsx (2)
  • SectionHeading (34-40)
  • SectionSubtitle (46-52)
apps/web/app/(app)/premium/PricingLazy.tsx (1)
apps/web/components/Loading.tsx (1)
  • Loading (3-9)
apps/web/app/(landing)/old-landing/page.tsx (1)
apps/web/app/(landing)/home/FinalCTA.tsx (1)
  • FinalCTA (11-36)
apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (2)
apps/web/components/new-landing/common/Section.tsx (1)
  • Section (9-15)
apps/web/components/new-landing/common/Typography.tsx (2)
  • SectionHeading (34-40)
  • SectionSubtitle (46-52)
apps/web/components/new-landing/sections/Footer.tsx (2)
apps/web/components/new-landing/UnicornScene.tsx (1)
  • UnicornScene (26-55)
apps/web/components/new-landing/FooterLineLogo.tsx (1)
  • FooterLineLogo (5-22)
apps/web/app/(landing)/oss-friends/page.tsx (1)
apps/web/app/(landing)/home/FinalCTA.tsx (1)
  • FinalCTA (11-36)
apps/web/app/(landing)/home/Hero.tsx (11)
apps/web/components/new-landing/common/Badge.tsx (2)
  • BadgeVariant (3-15)
  • Badge (24-99)
apps/web/components/new-landing/common/Section.tsx (2)
  • Section (9-15)
  • SectionContent (23-33)
apps/web/components/new-landing/common/Typography.tsx (2)
  • PageHeading (25-27)
  • Paragraph (80-113)
apps/web/components/new-landing/common/BlurFade.tsx (1)
  • BlurFade (31-72)
apps/web/components/new-landing/CallToAction.tsx (1)
  • CallToAction (16-45)
apps/web/components/new-landing/icons/Outlook.tsx (1)
  • Outlook (6-262)
apps/web/components/new-landing/icons/Gmail.tsx (1)
  • Gmail (6-78)
apps/web/hooks/useAnalytics.ts (1)
  • landingPageAnalytics (23-42)
apps/web/components/new-landing/LiquidGlassButton.tsx (1)
  • LiquidGlassButton (9-96)
apps/web/components/new-landing/icons/Play.tsx (1)
  • Play (5-77)
apps/web/components/new-landing/UnicornScene.tsx (1)
  • UnicornScene (26-55)
apps/web/app/(landing)/home/FAQs.tsx (6)
apps/web/components/new-landing/common/Anchor.tsx (1)
  • Anchor (11-21)
apps/web/env.ts (1)
  • env (16-244)
apps/web/components/new-landing/common/Section.tsx (2)
  • Section (9-15)
  • SectionContent (23-33)
apps/web/components/new-landing/common/Typography.tsx (2)
  • SectionHeading (34-40)
  • Paragraph (80-113)
apps/web/components/new-landing/common/CardWrapper.tsx (1)
  • CardWrapper (11-51)
apps/web/components/new-landing/common/Card.tsx (2)
  • Card (66-104)
  • CardContent (9-11)
apps/web/components/common/Banner.tsx (1)
apps/web/components/new-landing/UnicornScene.tsx (1)
  • UnicornScene (26-55)
apps/web/app/(landing)/page.tsx (7)
apps/web/components/layouts/BasicLayout.tsx (1)
  • BasicLayout (7-15)
apps/web/app/(landing)/home/Hero.tsx (2)
  • Hero (40-79)
  • HeroVideoPlayer (81-124)
apps/web/components/new-landing/common/WordReveal.tsx (1)
  • WordReveal (12-38)
apps/web/components/new-landing/BrandScroller.tsx (1)
  • BrandScroller (14-46)
apps/web/components/new-landing/sections/OrganizedInbox.tsx (1)
  • OrganizedInbox (16-39)
apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (1)
  • PreWrittenDrafts (16-39)
apps/web/components/new-landing/sections/StartedInMinutes.tsx (1)
  • StartedInMinutes (34-139)
apps/web/components/new-landing/BrandScroller.tsx (3)
apps/web/utils/brands.ts (1)
  • Brand (1-5)
apps/web/components/new-landing/common/Typography.tsx (1)
  • Paragraph (80-113)
apps/web/utils/config.ts (1)
  • userCount (8-8)
apps/web/components/layouts/BlogLayout.tsx (1)
apps/web/components/new-landing/sections/Footer.tsx (1)
  • Footer (137-185)
⏰ 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: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (16)
apps/web/app/(marketing) (1)

1-1: The original review comment is based on an incorrect assumption.

Verification reveals that apps/web/app/(marketing) is not a submodule:

  • No .gitmodules file exists in the repository
  • No submodules are registered
  • The directory is a regular directory, not a git repository
  • The directory is not staged as a submodule

The review comment incorrectly assumed this was a submodule pointer change. If this is a regular directory with code changes, the review should focus on the actual file modifications within that directory instead.

Likely an incorrect or invalid review comment.

apps/web/components/layouts/BlogLayout.tsx (3)

4-12: LGTM!

The BlogHeader component is well-structured with appropriate sticky positioning, z-index layering, and backdrop blur styling. The semi-transparent background (bg-white/75) correctly supports the backdrop blur effect.


19-24: Verify Footer padding on small screens.

The Footer is styled with px-0 which removes horizontal padding on small screens. This differs from the Header's px-6 on small screens and may cause edge-to-edge content rendering on mobile devices.

Ensure this is intentional for the blog layout. If content should have padding on mobile, consider changing px-0 to px-6:

       <Footer
         variant="simple"
-        className="mx-auto w-full max-w-screen-xl px-0 lg:px-20"
+        className="mx-auto w-full max-w-screen-xl px-6 lg:px-20"
       />

1-1: Code is correct—Header component properly accepts and applies the className prop.

Verification confirms that the Header component at line 15 of apps/web/components/new-landing/sections/Header.tsx accepts className as part of HeaderProps and correctly merges it into the <header> element using the cn() utility function (line 22), alongside the default styles. The className prop is properly consumed.

apps/web/components/common/Banner.tsx (2)

1-2: LGTM!

Import path is correct and the UnicornScene component is properly referenced.


3-6: LGTM!

The interface is well-defined with appropriate types for flexible content rendering.

apps/web/components/new-landing/sections/StartedInMinutes.tsx (1)

29-38: Prop‑driven section title/subtitle look good

Switching StartedInMinutes to accept title and subtitle props is clean and keeps the existing layout intact while making the section reusable. No issues from a type or runtime perspective.

apps/web/app/(landing)/oss-friends/page.tsx (1)

8-77: Using FinalCTA here keeps CTAs consistent across landing pages

Importing and rendering FinalCTA before the Footer cleanly aligns this page with the rest of the landing experience without touching existing OSS Friends logic. Looks good as-is.

apps/web/app/(landing)/old-landing/page.tsx (1)

9-9: LGTM! Clean component consolidation.

The switch from CTA to FinalCTA aligns with the broader refactoring effort across the landing pages.

Also applies to: 22-22

apps/web/app/(landing)/page.tsx (1)

22-67: Excellent compositional refactor!

The new Hero composition with WordReveal, HeroVideoPlayer, and BrandScroller as children is well-structured. The prop-driven section components (OrganizedInbox, PreWrittenDrafts, StartedInMinutes) follow a consistent pattern that improves maintainability and reusability.

The use of key="actually" on the <em> element in the WordReveal words array is good practice for React reconciliation.

apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (1)

11-20: LGTM! Clean prop-driven refactor.

Converting this component to accept title and subtitle props improves reusability and maintains consistency with the other refactored section components.

apps/web/components/new-landing/BrandScroller.tsx (1)

9-46: LGTM! Well-structured prop-driven component.

The addition of brandList and animate props with sensible defaults makes this component flexible and reusable. The user count display via Paragraph is a nice touch.

apps/web/components/layouts/BasicLayout.tsx (1)

9-13: Verify background color handling.

The root div no longer has bg-white applied. While this is likely intentional for the design refresh, please ensure the background color is properly handled either by a parent component or global styles to avoid unexpected visual issues.

apps/web/components/new-landing/sections/OrganizedInbox.tsx (1)

11-20: LGTM! Consistent prop-driven pattern.

This refactor follows the same clean pattern as PreWrittenDrafts and other section components, making the component reusable and content-agnostic.

apps/web/app/(landing)/home/Hero.tsx (2)

1-1: Excellent Hero component refactor!

The "use client" directive is correctly placed since HeroVideoPlayer uses the usePostHog hook. The new prop-driven API with optional badge, title, subtitle, and children provides excellent flexibility for composition while maintaining clean structure.

The staggered BlurFade animations create a nice progressive reveal effect.

Also applies to: 32-79


81-124: Well-structured video player component.

The HeroVideoPlayer implementation is clean with proper:

  • Dialog/DialogTrigger composition for modal behavior
  • Analytics tracking via landingPageAnalytics.videoClicked
  • Accessibility with sr-only DialogTitle
  • YouTube embed with autoplay and proper iframe attributes
  • UnicornScene background effect

Copy link
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 23 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/app/(landing)/home/FAQs.tsx">

<violation number="1" location="apps/web/app/(landing)/home/FAQs.tsx:25">
Links opened in a new tab need `rel=&quot;noopener noreferrer&quot;`; these new FAQ GitHub links now target `_blank` without that protection, exposing users to tabnabbing. Please ensure the Anchor component (or these usages) add the proper rel attributes.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

answer: (
<span>
Email us or post an issue on{" "}
<Anchor href="/github" newTab>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 16, 2025

Choose a reason for hiding this comment

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

Links opened in a new tab need rel="noopener noreferrer"; these new FAQ GitHub links now target _blank without that protection, exposing users to tabnabbing. Please ensure the Anchor component (or these usages) add the proper rel attributes.

Prompt for AI agents
Address the following comment on apps/web/app/(landing)/home/FAQs.tsx at line 25:

<comment>Links opened in a new tab need `rel=&quot;noopener noreferrer&quot;`; these new FAQ GitHub links now target `_blank` without that protection, exposing users to tabnabbing. Please ensure the Anchor component (or these usages) add the proper rel attributes.</comment>

<file context>
@@ -1,100 +1,98 @@
+    answer: (
+      &lt;span&gt;
+        Email us or post an issue on{&quot; &quot;}
+        &lt;Anchor href=&quot;/github&quot; newTab&gt;
+          GitHub
+        &lt;/Anchor&gt;
</file context>
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/web/app/(landing)/home/Hero.tsx (1)

32-80: Hero composition and CTA flow look solid; consider tightening prop requirements

The new Hero surface (badge → heading → subtitle → CTA → integrations → children) is clean and matches the new Section/BlurFade patterns.

Two small API/UX tweaks you might consider:

  • title is central to this section; making it required in HeroProps would prevent a “blank” hero from being rendered.
  • subtitle is rendered unconditionally; conditionally rendering the <Paragraph> only when subtitle is provided would avoid empty spacing on variants that don’t need copy.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a3dbf6 and 962fb4c.

📒 Files selected for processing (3)
  • apps/web/app/(landing)/home/Hero.tsx (1 hunks)
  • apps/web/app/(marketing) (1 hunks)
  • apps/web/components/new-landing/BrandScroller.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(marketing)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/components/new-landing/BrandScroller.tsx (4)
apps/web/utils/brands.ts (1)
  • Brand (1-5)
apps/web/components/new-landing/common/BlurFade.tsx (1)
  • BlurFade (31-72)
apps/web/components/new-landing/common/Typography.tsx (1)
  • Paragraph (80-113)
apps/web/utils/config.ts (1)
  • userCount (8-8)
apps/web/app/(landing)/home/Hero.tsx (11)
apps/web/components/new-landing/common/Badge.tsx (2)
  • BadgeVariant (3-15)
  • Badge (24-99)
apps/web/components/new-landing/common/Section.tsx (2)
  • Section (9-15)
  • SectionContent (23-33)
apps/web/components/new-landing/common/BlurFade.tsx (1)
  • BlurFade (31-72)
apps/web/components/new-landing/common/Typography.tsx (2)
  • PageHeading (25-27)
  • Paragraph (80-113)
apps/web/components/new-landing/CallToAction.tsx (1)
  • CallToAction (16-45)
apps/web/components/new-landing/icons/Outlook.tsx (1)
  • Outlook (6-262)
apps/web/components/new-landing/icons/Gmail.tsx (1)
  • Gmail (6-78)
apps/web/hooks/useAnalytics.ts (1)
  • landingPageAnalytics (23-42)
apps/web/components/new-landing/LiquidGlassButton.tsx (1)
  • LiquidGlassButton (9-96)
apps/web/components/new-landing/icons/Play.tsx (1)
  • Play (5-77)
apps/web/components/new-landing/UnicornScene.tsx (1)
  • UnicornScene (26-55)
⏰ 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: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (1)
apps/web/components/new-landing/BrandScroller.tsx (1)

10-18: Prop-driven BrandScroller API looks good

The brandList + animate props with sensible defaults (BRANDS_LIST.default, true) make the component reusable without breaking existing behavior.

Comment on lines +83 to 126
export function HeroVideoPlayer() {
const posthog = usePostHog();

return (
<div className="flex flex-col items-center justify-center gap-2 sm:flex-row">
<Link
// href="https://www.producthunt.com/posts/inbox-zero-2?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-inbox&#0045;zero&#0045;2"
href="https://www.producthunt.com/products/inbox-zero-tabs"
target="_blank"
rel="noreferrer"
>
<Image
src="/images/home/product-hunt-badge.svg"
alt="Inbox&#0032;Zero | Product Hunt"
className="h-[54px] w-[250px]"
width="250"
height="54"
/>
</Link>
</div>
<BlurFade delay={0.125 * 9}>
<div className="relative w-full">
<div className="relative border border-[#EFEFEF] rounded-3xl md:rounded-[43px] overflow-hidden block">
<Dialog>
<DialogTrigger
asChild
onClick={() => landingPageAnalytics.videoClicked(posthog)}
>
<LiquidGlassButton className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<div>
<Play className="translate-x-[2px]" />
</div>
</LiquidGlassButton>
</DialogTrigger>
<DialogContent className="max-w-7xl border-0 bg-transparent p-0">
<DialogTitle className="sr-only">Video player</DialogTitle>
<div className="relative aspect-video w-full">
<iframe
src="https://www.youtube.com/embed/hfvKvTHBjG0?autoplay=1&rel=0"
className="size-full rounded-lg"
title="Video content"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
</div>
</DialogContent>
</Dialog>
<Image
src="/images/new-landing/video-thumbnail.png"
alt="an organized inbox"
width={2000}
height={1000}
className="w-full"
/>
<UnicornScene className="h-[calc(100%+5px)] opacity-30" />
</div>
</div>
</BlurFade>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add an accessible label to the video play button and refine video titles

The video CTA is an icon-only LiquidGlassButton wrapped in DialogTrigger. As-is, the button’s accessible name comes only from the SVG, which isn’t a helpful label for screen readers.

Consider giving the button an explicit label:

-              <LiquidGlassButton className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
+              <LiquidGlassButton
+                className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
+                aria-label="Play inbox zero demo video"
+              >
                 <div>
                   <Play className="translate-x-[2px]" />
                 </div>
               </LiquidGlassButton>

Optionally, you could:

  • Make DialogTitle and the iframe title more descriptive (e.g., “Inbox Zero product demo video”) to improve screen reader context.
  • If desired, ensure the iframe is only mounted when the dialog is open (depending on the underlying Dialog implementation) to avoid any unnecessary background loading, though the current setup is likely acceptable.
📝 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
export function HeroVideoPlayer() {
const posthog = usePostHog();
return (
<div className="flex flex-col items-center justify-center gap-2 sm:flex-row">
<Link
// href="https://www.producthunt.com/posts/inbox-zero-2?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-inbox&#0045;zero&#0045;2"
href="https://www.producthunt.com/products/inbox-zero-tabs"
target="_blank"
rel="noreferrer"
>
<Image
src="/images/home/product-hunt-badge.svg"
alt="Inbox&#0032;Zero | Product Hunt"
className="h-[54px] w-[250px]"
width="250"
height="54"
/>
</Link>
</div>
<BlurFade delay={0.125 * 9}>
<div className="relative w-full">
<div className="relative border border-[#EFEFEF] rounded-3xl md:rounded-[43px] overflow-hidden block">
<Dialog>
<DialogTrigger
asChild
onClick={() => landingPageAnalytics.videoClicked(posthog)}
>
<LiquidGlassButton className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<div>
<Play className="translate-x-[2px]" />
</div>
</LiquidGlassButton>
</DialogTrigger>
<DialogContent className="max-w-7xl border-0 bg-transparent p-0">
<DialogTitle className="sr-only">Video player</DialogTitle>
<div className="relative aspect-video w-full">
<iframe
src="https://www.youtube.com/embed/hfvKvTHBjG0?autoplay=1&rel=0"
className="size-full rounded-lg"
title="Video content"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
</div>
</DialogContent>
</Dialog>
<Image
src="/images/new-landing/video-thumbnail.png"
alt="an organized inbox"
width={2000}
height={1000}
className="w-full"
/>
<UnicornScene className="h-[calc(100%+5px)] opacity-30" />
</div>
</div>
</BlurFade>
);
}
export function HeroVideoPlayer() {
const posthog = usePostHog();
return (
<BlurFade delay={0.125 * 9}>
<div className="relative w-full">
<div className="relative border border-[#EFEFEF] rounded-3xl md:rounded-[43px] overflow-hidden block">
<Dialog>
<DialogTrigger
asChild
onClick={() => landingPageAnalytics.videoClicked(posthog)}
>
<LiquidGlassButton
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
aria-label="Play inbox zero demo video"
>
<div>
<Play className="translate-x-[2px]" />
</div>
</LiquidGlassButton>
</DialogTrigger>
<DialogContent className="max-w-7xl border-0 bg-transparent p-0">
<DialogTitle className="sr-only">Video player</DialogTitle>
<div className="relative aspect-video w-full">
<iframe
src="https://www.youtube.com/embed/hfvKvTHBjG0?autoplay=1&rel=0"
className="size-full rounded-lg"
title="Video content"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
</div>
</DialogContent>
</Dialog>
<Image
src="/images/new-landing/video-thumbnail.png"
alt="an organized inbox"
width={2000}
height={1000}
className="w-full"
/>
<UnicornScene className="h-[calc(100%+5px)] opacity-30" />
</div>
</div>
</BlurFade>
);
}
🤖 Prompt for AI Agents
In apps/web/app/(landing)/home/Hero.tsx around lines 83 to 126 the play CTA is
an icon-only LiquidGlassButton inside DialogTrigger and lacks an explicit
accessible name; add an accessible label to the button (e.g., aria-label="Play
Inbox Zero product demo" or include visually hidden text inside the trigger) so
screen readers get a meaningful name, and update DialogTitle and the iframe
title to a more descriptive string like "Inbox Zero product demo video";
optionally, if you want to avoid background loading, render the iframe only when
the Dialog is open (use Dialog state or conditional rendering provided by your
Dialog implementation).

Comment on lines +20 to 45
<BlurFade duration={0.4} delay={0.125 * 10}>
<div className="mt-12">
<Paragraph>Join {userCount} users saving hours daily</Paragraph>
<div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
{new Array(4).fill(0).map((_, i) => (
<div
className={cx(
"flex shrink-0 justify-around [margin-right:var(--gap)] [gap:var(--gap)] flex-row [--duration:100s] opacity-90",
animate ? "animate-marquee" : "",
)}
key={i}
>
{brandList.map(({ alt, src, height }) => (
<div className="flex items-start" key={alt}>
<Image
src={src}
alt={alt}
width={100}
height={100}
className={cx("w-auto", height || "h-5 sm:h-6 md:h-8")}
/>
</div>
))}
</div>
))}
</div>
Copy link
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 malformed Tailwind arbitrary gap class and consider logo accessibility

There’s a typo in the container classes:

  • "[gap:var(--gap))]" has an extra ) and will not generate the intended gap rule.

This likely breaks the intended spacing between the marquee items.

You can fix the spacing class with:

-        <div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
+        <div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap)] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">

Optional: since each logo row is repeated 4 times for the marquee, consider marking the scroller or duplicate rows as decorative (e.g., aria-hidden="true") so screen readers don’t announce the same brand names multiple times.

📝 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
<BlurFade duration={0.4} delay={0.125 * 10}>
<div className="mt-12">
<Paragraph>Join {userCount} users saving hours daily</Paragraph>
<div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
{new Array(4).fill(0).map((_, i) => (
<div
className={cx(
"flex shrink-0 justify-around [margin-right:var(--gap)] [gap:var(--gap)] flex-row [--duration:100s] opacity-90",
animate ? "animate-marquee" : "",
)}
key={i}
>
{brandList.map(({ alt, src, height }) => (
<div className="flex items-start" key={alt}>
<Image
src={src}
alt={alt}
width={100}
height={100}
className={cx("w-auto", height || "h-5 sm:h-6 md:h-8")}
/>
</div>
))}
</div>
))}
</div>
<BlurFade duration={0.4} delay={0.125 * 10}>
<div className="mt-12">
<Paragraph>Join {userCount} users saving hours daily</Paragraph>
<div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap)] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
{new Array(4).fill(0).map((_, i) => (
<div
className={cx(
"flex shrink-0 justify-around [margin-right:var(--gap)] [gap:var(--gap)] flex-row [--duration:100s] opacity-90",
animate ? "animate-marquee" : "",
)}
key={i}
>
{brandList.map(({ alt, src, height }) => (
<div className="flex items-start" key={alt}>
<Image
src={src}
alt={alt}
width={100}
height={100}
className={cx("w-auto", height || "h-5 sm:h-6 md:h-8")}
/>
</div>
))}
</div>
))}
</div>
🤖 Prompt for AI Agents
In apps/web/components/new-landing/BrandScroller.tsx around lines 20-45, the
Tailwind arbitrary gap class is malformed ("[gap:var(--gap))]" with an extra
")") which prevents the intended spacing; replace it with the correct
"[gap:var(--gap)]" and ensure the surrounding class string remains valid;
additionally, mark duplicated marquee rows or the scroller wrapper as decorative
(e.g., add aria-hidden="true" to the repeated rows or to the outer marquee
container) so screen readers do not announce the same brand names multiple
times.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 9 files (reviewed changes from recent commits).

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/components/new-landing/BrandScroller.tsx">

<violation number="1" location="apps/web/components/new-landing/BrandScroller.tsx:23">
Malformed Tailwind arbitrary property in the class string: &quot;[gap:var(--gap))]&quot; has an extra &quot;)&quot;, so the gap rule won’t apply. Replace with &quot;[gap:var(--gap)]&quot; to fix spacing between marquee items.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

<BlurFade duration={0.4} delay={0.125 * 10}>
<div className="mt-12">
<Paragraph>Join {userCount} users saving hours daily</Paragraph>
<div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 16, 2025

Choose a reason for hiding this comment

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

Malformed Tailwind arbitrary property in the class string: "[gap:var(--gap))]" has an extra ")", so the gap rule won’t apply. Replace with "[gap:var(--gap)]" to fix spacing between marquee items.

Prompt for AI agents
Address the following comment on apps/web/components/new-landing/BrandScroller.tsx at line 23:

<comment>Malformed Tailwind arbitrary property in the class string: &quot;[gap:var(--gap))]&quot; has an extra &quot;)&quot;, so the gap rule won’t apply. Replace with &quot;[gap:var(--gap)]&quot; to fix spacing between marquee items.</comment>

<file context>
@@ -16,31 +17,33 @@ export const BrandScroller = ({
+    &lt;BlurFade duration={0.4} delay={0.125 * 10}&gt;
+      &lt;div className=&quot;mt-12&quot;&gt;
+        &lt;Paragraph&gt;Join {userCount} users saving hours daily&lt;/Paragraph&gt;
+        &lt;div className=&quot;group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]&quot;&gt;
+          {new Array(4).fill(0).map((_, i) =&gt; (
+            &lt;div
</file context>
Suggested change
<div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap))] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
+ <div className="group flex overflow-x-hidden py-10 [--gap:2rem] md:[--gap:3rem] [gap:var(--gap)] flex-row max-w-full [mask-image:linear-gradient(to_right,_rgba(0,_0,_0,_0),rgba(0,_0,_0,_1)_10%,rgba(0,_0,_0,_1)_90%,rgba(0,_0,_0,_0))]">
Fix with Cubic

Copy link
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 3 files (reviewed changes from recent commits).

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/app/(app)/premium/OldPricingLazy.tsx">

<violation number="1" location="apps/web/app/(app)/premium/OldPricingLazy.tsx:6">
Enable Suspense on the dynamic Pricing component so the surrounding &lt;Suspense&gt; boundary functions instead of throwing and ignoring the fallback.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

import { Suspense } from "react";
import type { PricingProps } from "./Pricing";

const PricingComponent = dynamic(() => import("./Pricing"));
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 16, 2025

Choose a reason for hiding this comment

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

Enable Suspense on the dynamic Pricing component so the surrounding boundary functions instead of throwing and ignoring the fallback.

Prompt for AI agents
Address the following comment on apps/web/app/(app)/premium/OldPricingLazy.tsx at line 6:

<comment>Enable Suspense on the dynamic Pricing component so the surrounding &lt;Suspense&gt; boundary functions instead of throwing and ignoring the fallback.</comment>

<file context>
@@ -0,0 +1,12 @@
+import { Suspense } from &quot;react&quot;;
+import type { PricingProps } from &quot;./Pricing&quot;;
+
+const PricingComponent = dynamic(() =&gt; import(&quot;./Pricing&quot;));
+
+export const OldPricingLazy = (props: PricingProps) =&gt; (
</file context>
Suggested change
const PricingComponent = dynamic(() => import("./Pricing"));
const PricingComponent = dynamic(() => import("./Pricing"), { suspense: true });
Fix with Cubic

@elie222 elie222 merged commit e183362 into elie222:main Nov 18, 2025
15 of 17 checks passed
This was referenced Nov 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants