Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Sep 18, 2025

Introduces dedicated feature pages for Instant Mode and Studio Mode, including their configs, metadata, and page components. Updates the Navbar to highlight these modes with icons and improves dropdown rendering. Adds a generic FeaturePage component, supporting SEO enhancements and structured data. Refactors SEO types and templates to support video iframes, badges, and secondary CTAs. Adds feature page transformation utilities and supporting types.

Summary by CodeRabbit

  • New Features

    • Added Instant Mode and Studio Mode feature pages with rich content (hero, features, use cases, comparisons, workflow, FAQs, video, CTA).
    • Added Instant Mode and Studio Mode to the Product menu; Pricing moved to the end.
  • Enhancements

    • Updated feature page template: hero badge, secondary CTA, responsive CTA layout, iframe video support, and improved SEO/FAQ structured data.
    • Improved Product dropdown layout with icons, labels, and subtitles.
  • Bug Fixes

    • Made Recording Modes section conditional to avoid rendering errors when data is missing.

Introduces dedicated feature pages for Instant Mode and Studio Mode, including their configs, metadata, and page components. Updates the Navbar to highlight these modes with icons and improves dropdown rendering. Adds a generic FeaturePage component, supporting SEO enhancements and structured data. Refactors SEO types and templates to support video iframes, badges, and secondary CTAs. Adds feature page transformation utilities and supporting types.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Instant Mode and Studio Mode feature pages with routes, metadata, icons, and data-driven content; introduces a shared FeaturePage component plus transforms/JSON‑LD; updates SeoPageTemplate and types to support badges, iframe videos, and secondary CTAs; adjusts Navbar product dropdown and link order.

Changes

Cohort / File(s) Summary
Navbar updates
apps/web/app/(site)/Navbar.tsx
Adds Product dropdown items for Instant Mode and Studio Mode (labels, subtitles, hrefs, icons); reorders Links (moves Pricing) and replaces sublink rendering with enhanced list items and hover/focus styling.
Instant Mode feature page
apps/web/app/(site)/features/instant-mode/InstantModePage.tsx, apps/web/app/(site)/features/instant-mode/page.tsx
New client Feature page and App Router page with FeaturePageConfig, icons (Zap), metadata export, and default Page rendering InstantModePage via shared FeaturePage.
Studio Mode feature page
apps/web/app/(site)/features/studio-mode/StudioModePage.tsx, apps/web/app/(site)/features/studio-mode/page.tsx
New client Feature page and App Router page with FeaturePageConfig, icons (Clapperboard), metadata export, and default Page rendering StudioModePage via shared FeaturePage.
Feature page component
apps/web/components/features/FeaturePage.tsx, apps/web/lib/features/index.ts
New client component that transforms FeaturePageConfig to SeoPageContent, injects JSON‑LD script, and renders SeoPageTemplate; re-exports feature utilities and types.
SEO template and types
apps/web/components/seo/SeoPageTemplate.tsx, apps/web/components/seo/types.ts
Adds hero badge rendering and conditional animation delays, supports video.iframe, secondary CTA, responsive CTA layout changes, removes testimonials, and updates SeoPageContent types (badge, iframe, optional video fields, secondaryButtonText).
Feature data contract and transforms
apps/web/lib/features/types.ts, apps/web/lib/features/transform.ts
New Feature page type definitions and transform utilities: transformFeaturePageToSeo (maps config→SeoPageContent, handles customIcons, optional sections) and createFeaturePageStructuredData (JSON‑LD FAQ generation).
Agencies page guard
apps/web/components/pages/seo/AgenciesPage.tsx
Adds conditional guard around Recording Modes section to avoid rendering when recordingModes is absent.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NextPage as Next.js Route (instant|studio)
  participant FeaturePage as FeaturePage Component
  participant Transform as transformFeaturePageToSeo
  participant JSONLD as createFeaturePageStructuredData
  participant SeoTemplate as SeoPageTemplate

  Note over NextPage: imports config + customIcons
  User->>NextPage: Request /features/(instant|studio)
  NextPage->>FeaturePage: render({ config, customIcons, flags })
  FeaturePage->>Transform: build SeoPageContent from config (+icons)
  FeaturePage->>JSONLD: build FAQ JSON‑LD from config.faq
  FeaturePage->>SeoTemplate: render with SeoPageContent (badge, video.iframe?, CTAs)
  FeaturePage-->>User: inject <script type="application/ld+json">
  SeoTemplate-->>User: HTML (hero, features, use cases, video, CTA)
Loading
sequenceDiagram
  participant Config as FeaturePageConfig
  participant Transform as transformFeaturePageToSeo
  participant SeoContent as SeoPageContent

  Note left of Config: Optional sections: comparison, workflow, video
  Config->>Transform: content
  Transform->>SeoContent: map hero, features, useCases, faqs, cta
  alt comparison present
    Transform->>SeoContent: recordingModes with icons (customIcons fallback)
  end
  alt workflow present
    Transform->>SeoContent: migrationGuide steps
  end
  alt video.iframe present
    Transform->>SeoContent: video.iframe content
  else
    Transform->>SeoContent: default video iframe stub or mux fallback
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Brendonovich

Poem

In burrows of code I twitch my nose,
New modes arrive where the navbar goes.
Zap and clap, the pages bloom,
Badges glow and videos zoom.
JSON‑LD tucked safe below—thump! Off to staging we go. 🐇🎬⚡

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 "feat: Add Instant and Studio Mode feature pages + feature templating system" is concise and accurately summarizes the primary changes in this PR—adding Instant and Studio Mode pages and a reusable FeaturePage/templating system—and it aligns with the PR summary and file-level changes (Navbar, new pages, FeaturePage, transform/types). It is specific, clear, and suitable for teammates scanning the commit history.

📜 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 dabca3b and 418231c.

📒 Files selected for processing (1)
  • apps/web/components/pages/seo/AgenciesPage.tsx (1 hunks)

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.

Wraps the Recording Modes section in a conditional check for agenciesContent.recordingModes to prevent rendering errors if the data is missing.
@richiemcilroy richiemcilroy merged commit 18c900e into main Sep 18, 2025
10 of 11 checks passed
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: 4

🧹 Nitpick comments (25)
apps/web/app/(site)/Navbar.tsx (5)

16-16: Icon a11y and theming: prefer stroke color via Tailwind, mark decorative icons hidden

Use lucide’s stroke color instead of inline fill and mark icons aria-hidden to avoid noise for screen readers.

-import { Clapperboard, Zap } from "lucide-react";
+import { Clapperboard, Zap } from "lucide-react";

Apply in the items below (see diff for lines 28-45).


28-45: Tighten icon styling and a11y

Inline fill colors are inconsistent with Tailwind tokens and can render oddly in dark modes. Also, these icons are decorative—hide them from AT.

-        icon: <Zap fill="yellow" className="size-4" strokeWidth={1.5} />,
+        icon: <Zap className="size-4 text-yellow-500" strokeWidth={1.5} aria-hidden />,
...
-        icon: (
-          <Clapperboard
-            fill="var(--blue-9)"
-            className="size-4"
-            strokeWidth={1.5}
-          />
-        ),
+        icon: (
+          <Clapperboard
+            className="size-4 text-blue-9"
+            strokeWidth={1.5}
+            aria-hidden
+          />
+        ),

166-183: Fix group-hover, and use Next.js Link for internal/external routes

  • group-hover isn’t applied because the parent lacks the group class.
  • Using loses Next.js routing/prefetch. Wrap with Link as the child of NavigationMenuLink.
-            <li key={sublink.href}>
-              <NavigationMenuLink asChild>
-                <a
-                  href={sublink.href}
-                  className="block p-3 space-y-1 leading-none no-underline rounded-md transition-all duration-200 outline-none select-none hover:bg-gray-2 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
-                >
+            <li key={sublink.href}>
+              <NavigationMenuLink asChild>
+                <Link
+                  href={sublink.href}
+                  className="group block p-3 space-y-1 leading-none no-underline rounded-md transition-all duration-200 outline-none select-none hover:bg-gray-2 focus:bg-accent"
+                  prefetch
+                  {...(sublink.href.startsWith("http")
+                    ? { target: "_blank", rel: "noopener noreferrer" }
+                    : {})}
+                >
                   <div className="flex items-center gap-2 text-base font-medium leading-none transition-colors duration-200 text-zinc-700 group-hover:text-zinc-900">
                     {sublink.icon && sublink.icon}
                     <span className="text-gray-12 font-semibold">
                       {sublink.label}
                     </span>
                   </div>
-                  <p className="text-sm leading-snug transition-colors duration-200 line-clamp-2 text-zinc-500 group-hover:text-zinc-700">
+                  <p className="text-sm leading-snug transition-colors duration-200 line-clamp-2 text-zinc-500 group-hover:text-zinc-700">
                     {sublink.sub}
                   </p>
-                </a>
+                </Link>
               </NavigationMenuLink>
             </li>

189-198: Invalid Tailwind class: focus:text-8

focus:text-8 isn’t a valid token; likely meant focus:text-blue-9 (or remove).

-                                "px-2 py-0 text-sm font-medium hover:text-blue-9 focus:text-8",
+                                "px-2 py-0 text-sm font-medium hover:text-blue-9 focus:text-blue-9",

3-14: Remove unused import

ListItem is no longer used after the dropdown refactor.

-  ListItem,
apps/web/components/seo/types.ts (2)

29-36: Safer typing for iframe.src

Constrain iframe src to https at the type level to prevent accidental javascript: or data: usage. This doesn’t replace runtime validation, but helps.

+type HttpsUrl = `https://${string}`;
 export interface SeoPageContent {
 ...
   video: {
-    url?: string;
+    url?: string;
     thumbnail?: string;
     alt?: string;
     iframe?: {
-      src: string;
+      src: HttpsUrl;
       title?: string;
     };
   };

37-41: CTA supports secondary text — consider adding hrefs for flexibility

Hardcoding /download and /pricing in the template reduces reuse. Consider adding cta.primaryHref and cta.secondaryHref.

apps/web/components/seo/SeoPageTemplate.tsx (3)

186-203: Make CTA destinations data-driven, not hardcoded

Current hrefs are fixed to /download and /pricing. Use fields in content.cta for URLs.

-  <MotionButton variant="blue" href="/download" size="lg" className="relative z-[20] w-full font-medium text-md sm:w-auto">
+  <MotionButton variant="blue" href={content.cta.primaryHref ?? "/download"} size="lg" className="relative z-[20] w-full font-medium text-md sm:w-auto">
     {content.cta.buttonText}
   </MotionButton>
-  {content.cta.secondaryButtonText && (
+  {content.cta.secondaryButtonText && (
     <MotionButton
       variant="white"
-      href="/pricing"
+      href={content.cta.secondaryHref ?? "/pricing"}
       size="lg"
       className="relative z-[20] w-full font-medium text-md sm:w-auto"
     >
       {content.cta.secondaryButtonText}
     </MotionButton>
   )}

Add these fields in SeoPageContent.cta.


561-578: Bottom CTA: mirror data-driven URLs

Same as hero CTA: avoid hardcoded /download and /pricing.

-  <Button variant="blue" href="/download" size="lg" className="px-8 py-3 w-full font-medium transition-all duration-300 sm:w-auto sm:max-w-fit">
+  <Button variant="blue" href={content.cta.primaryHref ?? "/download"} size="lg" className="px-8 py-3 w-full font-medium transition-all duration-300 sm:w-auto sm:max-w-fit">
     {content.cta.buttonText}
   </Button>
-  {content.cta.secondaryButtonText && (
+  {content.cta.secondaryButtonText && (
     <Button
       variant="white"
-      href="/pricing"
+      href={content.cta.secondaryHref ?? "/pricing"}
       size="lg"
       className="px-8 py-3 w-full font-medium transition-all duration-300 sm:w-auto sm:max-w-fit"
     >
       {content.cta.secondaryButtonText}
     </Button>
   )}

495-519: FAQ toggle a11y: add aria-expanded and aria-controls

Improve accessibility for screen reader users.

-  <button
+  <button
     type="button"
     className="flex justify-between items-center px-6 py-4 w-full text-left"
-    onClick={() => toggleFaq(index)}
+    onClick={() => toggleFaq(index)}
+    aria-expanded={openFaqIndex === index}
+    aria-controls={`faq-panel-${index}`}
   >
...
-  <motion.div
+  <motion.div
     initial={{ height: 0, opacity: 0 }}
     animate={{ height: "auto", opacity: 1 }}
     exit={{ height: 0, opacity: 0 }}
     transition={{ duration: 0.3 }}
-    className="overflow-hidden"
+    className="overflow-hidden"
+    id={`faq-panel-${index}`}
   >
apps/web/lib/features/types.ts (3)

53-62: Video model: align with Seo types and add typed constraints

Consider aligning with SeoPageContent by typing iframe src as https and allowing optional Mux fields.

 export interface FeaturePageVideo {
   iframe?: {
-    src: string;
+    src: `https://${string}`;
     title?: string;
   };
   mux?: {
     playbackId: string;
     title?: string;
   };
 }

71-91: CTA: add href fields for primary/secondary

Matches the template suggestions and avoids hardcoded routes.

 export interface FeaturePageCta {
   title: string;
   description?: string;
   primaryButton: string;
+  primaryHref?: string;
   secondaryButton?: string;
+  secondaryHref?: string;
 }

10-47: Icon keys typed as strings — consider narrowing

If you maintain a custom icon map, define a union type (e.g., type IconKey = "zap" | "clapperboard" | ...) and use icon?: IconKey to catch typos at compile time.

Also applies to: 64-70, 93-106

apps/web/app/(site)/features/studio-mode/page.tsx (1)

4-32: Metadata looks solid — consider adding canonical URL

Adding alternates.canonical helps avoid duplicate content issues.

 export const metadata: Metadata = {
   title: "Studio Mode - Professional Screen Recording | Cap",
   description:
     "Create professional-quality screen recordings with Cap Studio Mode. Local recording, 4K 60fps quality, precision editing tools, and complete privacy control.",
   openGraph: {
@@
   twitter: {
     card: "summary_large_image",
     title: "Studio Mode - Professional Screen Recording | Cap",
     description:
       "Create professional-quality screen recordings with Cap Studio Mode. Local recording, 4K 60fps quality, precision editing tools, and complete privacy control.",
     images: ["https://cap.so/og.png"],
   },
+  alternates: {
+    canonical: "https://cap.so/features/studio-mode",
+  },
 };
apps/web/app/(site)/features/instant-mode/page.tsx (1)

4-32: Add canonical URL and reduce metadata duplication risk

  • Add alternates.canonical for SEO.
  • Consider deriving these fields from instantModeConfig to avoid future drift with on-page SEO content.
 export const metadata: Metadata = {
   title: "Instant Mode - Quick Screen Recording & Sharing | Cap",
   description:
     "Record and share instantly with Cap's cloud-powered Instant Mode. Get automatic transcriptions, collaborative comments, shareable links, and team workspaces for fast feedback loops.",
+  alternates: {
+    canonical: "https://cap.so/features/instant-mode",
+  },
   openGraph: {
apps/web/app/(site)/features/studio-mode/StudioModePage.tsx (3)

287-297: Use Tailwind for icon coloring; remove inline fill; add a11y

Inline fill and CSS vars violate the “Tailwind only” guideline. Use text color classes; mark decorative icons aria-hidden.

-  "studio-mode": (
-    <Clapperboard
-      fill="var(--blue-9)"
-      className="mb-4 size-8"
-      strokeWidth={1.5}
-    />
-  ),
-  "instant-mode": (
-    <Zap fill="yellow" className="mb-4 size-8" strokeWidth={1.5} />
-  ),
+  "studio-mode": (
+    <Clapperboard
+      aria-hidden="true"
+      className="mb-4 size-8 text-blue-600"
+      strokeWidth={1.5}
+    />
+  ),
+  "instant-mode": (
+    <Zap aria-hidden="true" className="mb-4 size-8 text-yellow-400" strokeWidth={1.5} />
+  ),

300-306: Remove redundant prop

showVideo is already controlled via config.customSections.showVideo; prop is unnecessary.

-      <FeaturePage
-        config={studioModeConfig}
-        customIcons={studioModeIcons}
-        showVideo={true}
-      />
+      <FeaturePage config={studioModeConfig} customIcons={studioModeIcons} />

7-285: Large static config in client bundle

This file is a client component; the heavy static config ships to the client. Prefer moving the config to a server module (no "use client") and pass as props to a thin client component.

I can draft a minimal refactor where page.tsx reads the config (RSC) and passes it to a ClientFeaturePage.

apps/web/components/features/FeaturePage.tsx (1)

3-5: Stabilize script id and minimize XSS surface

  • Use config.slug for a deterministic Script id; drop useId import.
  • Structured data path looks safe (answers are tag-stripped before JSON.stringify), but keep content HTML out of JSON-LD.
-import { useId } from "react";
+// no need for useId

@@
-  const scriptId = useId();
+  const scriptId = `faq-structured-data-${config.slug}`;

@@
-      <Script
-        id={`faq-structured-data-${scriptId}`}
+      <Script
+        id={scriptId}
         type="application/ld+json"
         dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
       />

Consider lazy stringifying to avoid work on re-renders:

- dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
+ dangerouslySetInnerHTML={{ __html: useMemo(() => JSON.stringify(structuredData), [structuredData]) }}

Also applies to: 31-41

apps/web/app/(site)/features/instant-mode/InstantModePage.tsx (4)

288-297: Use Tailwind for icon coloring; remove inline fill; add a11y

Same as Studio page.

-  "instant-mode": (
-    <Zap fill="yellow" className="mb-4 size-8" strokeWidth={1.5} />
-  ),
-  "studio-mode": (
-    <Clapperboard
-      fill="var(--blue-9)"
-      className="mb-4 size-8"
-      strokeWidth={1.5}
-    />
-  ),
+  "instant-mode": (
+    <Zap aria-hidden="true" className="mb-4 size-8 text-yellow-400" strokeWidth={1.5} />
+  ),
+  "studio-mode": (
+    <Clapperboard aria-hidden="true" className="mb-4 size-8 text-blue-600" strokeWidth={1.5} />
+  ),

300-306: Remove redundant prop

Same reasoning as Studio page.

-      <FeaturePage
-        config={instantModeConfig}
-        customIcons={instantModeIcons}
-        showVideo={true}
-      />
+      <FeaturePage config={instantModeConfig} customIcons={instantModeIcons} />

15-16: CTA copy consistency

Consider aligning primary CTA label with Studio page (“Download Cap Free”) or vice versa.

-      primaryCta: "Download for Free",
+      primaryCta: "Download Cap Free",
@@
-      primaryButton: "Download for Free",
+      primaryButton: "Download Cap Free",

Also applies to: 273-277


156-167: Asterisk note for “5-minute free recordings*

If you keep the asterisk, add a clarifying footnote in FAQ or near comparison to avoid ambiguity.

I can add a brief footnote entry under FAQ.

apps/web/lib/features/transform.ts (2)

12-14: Avoid leading period when subtitle is absent

Current interpolation can produce . Description when subtitle is empty.

-    description: `${content.hero.subtitle || ""}. ${content.hero.description}`,
+    description: content.hero.subtitle
+      ? `${content.hero.subtitle}. ${content.hero.description}`
+      : content.hero.description,

52-57: Optional: Make video optional to prevent empty iframes

Today a page without video gets a default empty iframe config. Prefer making video optional in SeoPageContent and rendering conditionally. Requires small change in SeoPageTemplate.

Want me to sketch the type change and template guard?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fe80cd and dabca3b.

📒 Files selected for processing (11)
  • apps/web/app/(site)/Navbar.tsx (4 hunks)
  • apps/web/app/(site)/features/instant-mode/InstantModePage.tsx (1 hunks)
  • apps/web/app/(site)/features/instant-mode/page.tsx (1 hunks)
  • apps/web/app/(site)/features/studio-mode/StudioModePage.tsx (1 hunks)
  • apps/web/app/(site)/features/studio-mode/page.tsx (1 hunks)
  • apps/web/components/features/FeaturePage.tsx (1 hunks)
  • apps/web/components/seo/SeoPageTemplate.tsx (4 hunks)
  • apps/web/components/seo/types.ts (2 hunks)
  • apps/web/lib/features/index.ts (1 hunks)
  • apps/web/lib/features/transform.ts (1 hunks)
  • apps/web/lib/features/types.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/app/(site)/features/studio-mode/page.tsx
  • apps/web/lib/features/index.ts
  • apps/web/lib/features/transform.ts
  • apps/web/lib/features/types.ts
  • apps/web/app/(site)/Navbar.tsx
  • apps/web/app/(site)/features/instant-mode/InstantModePage.tsx
  • apps/web/app/(site)/features/studio-mode/StudioModePage.tsx
  • apps/web/components/seo/types.ts
  • apps/web/app/(site)/features/instant-mode/page.tsx
  • apps/web/components/features/FeaturePage.tsx
  • apps/web/components/seo/SeoPageTemplate.tsx
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/app/(site)/features/studio-mode/page.tsx
  • apps/web/lib/features/index.ts
  • apps/web/lib/features/transform.ts
  • apps/web/lib/features/types.ts
  • apps/web/app/(site)/Navbar.tsx
  • apps/web/app/(site)/features/instant-mode/InstantModePage.tsx
  • apps/web/app/(site)/features/studio-mode/StudioModePage.tsx
  • apps/web/components/seo/types.ts
  • apps/web/app/(site)/features/instant-mode/page.tsx
  • apps/web/components/features/FeaturePage.tsx
  • apps/web/components/seo/SeoPageTemplate.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

**/*.{ts,tsx}: Use Biome to format/lint TypeScript with a 2-space indent
TypeScript file names should be kebab-case (e.g., user-menu.tsx)

Files:

  • apps/web/app/(site)/features/studio-mode/page.tsx
  • apps/web/lib/features/index.ts
  • apps/web/lib/features/transform.ts
  • apps/web/lib/features/types.ts
  • apps/web/app/(site)/Navbar.tsx
  • apps/web/app/(site)/features/instant-mode/InstantModePage.tsx
  • apps/web/app/(site)/features/studio-mode/StudioModePage.tsx
  • apps/web/components/seo/types.ts
  • apps/web/app/(site)/features/instant-mode/page.tsx
  • apps/web/components/features/FeaturePage.tsx
  • apps/web/components/seo/SeoPageTemplate.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

React/Solid components should be named using PascalCase

Files:

  • apps/web/app/(site)/features/studio-mode/page.tsx
  • apps/web/app/(site)/Navbar.tsx
  • apps/web/app/(site)/features/instant-mode/InstantModePage.tsx
  • apps/web/app/(site)/features/studio-mode/StudioModePage.tsx
  • apps/web/app/(site)/features/instant-mode/page.tsx
  • apps/web/components/features/FeaturePage.tsx
  • apps/web/components/seo/SeoPageTemplate.tsx
🧬 Code graph analysis (8)
apps/web/app/(site)/features/studio-mode/page.tsx (2)
apps/web/app/(site)/features/instant-mode/page.tsx (2)
  • metadata (4-32)
  • Page (34-36)
apps/web/app/(site)/features/studio-mode/StudioModePage.tsx (1)
  • StudioModePage (300-308)
apps/web/lib/features/transform.ts (2)
apps/web/lib/features/types.ts (1)
  • FeaturePageConfig (93-106)
apps/web/components/seo/types.ts (1)
  • SeoPageContent (7-73)
apps/web/app/(site)/Navbar.tsx (1)
packages/ui/src/components/NavigationMenu.tsx (1)
  • NavigationMenuLink (155-155)
apps/web/app/(site)/features/instant-mode/InstantModePage.tsx (2)
apps/web/lib/features/types.ts (1)
  • FeaturePageConfig (93-106)
apps/web/components/features/FeaturePage.tsx (1)
  • FeaturePage (21-51)
apps/web/app/(site)/features/studio-mode/StudioModePage.tsx (2)
apps/web/lib/features/types.ts (1)
  • FeaturePageConfig (93-106)
apps/web/components/features/FeaturePage.tsx (1)
  • FeaturePage (21-51)
apps/web/app/(site)/features/instant-mode/page.tsx (2)
apps/web/app/(site)/features/studio-mode/page.tsx (2)
  • metadata (4-32)
  • Page (34-36)
apps/web/app/(site)/features/instant-mode/InstantModePage.tsx (1)
  • InstantModePage (300-308)
apps/web/components/features/FeaturePage.tsx (3)
apps/web/lib/features/types.ts (1)
  • FeaturePageConfig (93-106)
apps/web/lib/features/transform.ts (2)
  • transformFeaturePageToSeo (5-74)
  • createFeaturePageStructuredData (76-93)
apps/web/components/seo/SeoPageTemplate.tsx (1)
  • SeoPageTemplate (74-585)
apps/web/components/seo/SeoPageTemplate.tsx (1)
packages/ui-solid/src/Button.tsx (1)
  • Button (40-50)
🪛 ast-grep (0.38.6)
apps/web/components/features/FeaturePage.tsx

[warning] 39-39: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ 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: Analyze (rust)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
🔇 Additional comments (10)
apps/web/app/(site)/Navbar.tsx (2)

109-112: Reordered Pricing link — LGTM

Order change reads fine and keeps URLs stable.


118-119: Confirmed — repo targets React 19 + Next 14; use() appears in client components

apps/web/package.json: react ^19.1.0, next 14.2.3. Instances of use() in client components: apps/web/app/(site)/Navbar.tsx (lines 116–119, 279–283), apps/web/app/Layout/PosthogIdentify.tsx (line 21), apps/web/app/Layout/Intercom/Client.tsx (line 9), apps/web/app/s/[videoId]/Share.tsx (line 157).

apps/web/components/seo/types.ts (1)

10-10: badge field — LGTM

apps/web/components/seo/SeoPageTemplate.tsx (3)

145-156: Hero badge animation — LGTM


160-169: Staggered animation based on badge — LGTM


24-31: Verify all HTML passed to renderHTML is developer-controlled or sanitized.

renderHTML is defined at apps/web/components/seo/SeoPageTemplate.tsx (≈lines 24–31) and similar helpers exist in apps/web/components/tools/ToolsPageTemplate.tsx and apps/web/components/blog/BlogTemplate.tsx (dangerouslySetInnerHTML used ~lines 76–86 and table cells ~205–206). These helpers are fed content from many places (apps/web/components/pages/seo/*Content, seoContent from transformFeaturePageToSeo, blog step.content, feature descriptions, FAQ answers, etc.). Confirm each source is static/config-only; if any source is CMS- or user-supplied, sanitize (e.g., DOMPurify) or whitelist tags/attributes before using dangerouslySetInnerHTML.

apps/web/lib/features/types.ts (1)

1-8: Hero model — LGTM

apps/web/app/(site)/features/studio-mode/page.tsx (1)

34-36: Page export — LGTM

apps/web/app/(site)/features/instant-mode/page.tsx (1)

34-36: LGTM

Server page rendering a client component is correct.

apps/web/lib/features/index.ts (1)

6-6: No action: export type * from "./types" is supported in TypeScript 5.0+ (project uses 5.8.3), so keep as-is.

Likely an incorrect or invalid review comment.

Comment on lines +256 to 257
See Studio Mode in Action
</h2>
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

Heading hardcodes “Studio Mode” in a shared template

This template is used by multiple pages; the heading will be wrong for Instant Mode.

-              See Studio Mode in Action
+              See Cap In Action

Optionally make this configurable via content.videoTitle.

📝 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
See Studio Mode in Action
</h2>
See Cap In Action
</h2>
🤖 Prompt for AI Agents
In apps/web/components/seo/SeoPageTemplate.tsx around lines 256 to 257, the H2
heading currently hardcodes "Studio Mode" which is incorrect for other pages
(e.g., Instant Mode); change the template to render a configurable title using
content.videoTitle (or a clear fallback like "Studio Mode" if content.videoTitle
is missing) so the heading reflects the page-specific title; update the JSX to
use the prop and ensure the prop is typed/optional in the component props and
any callers supply content.videoTitle where appropriate.

Comment on lines +264 to +301
{content.video.iframe ? (
<div
className="overflow-hidden w-full rounded-xl shadow-md"
style={{
aspectRatio: "16/9",
width: "100%",
position: "relative",
paddingBottom: "56.25%",
height: 0,
}}
/>
</div>
>
<iframe
src={content.video.iframe.src}
title={content.video.iframe.title || "Cap Demo"}
frameBorder="0"
allowFullScreen
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
borderRadius: "0.75rem",
}}
/>
</div>
) : (
<div className="overflow-hidden rounded-xl shadow-md">
<MuxPlayer
playbackId="A6oZoUWVZjOIVZB6XnBMLagYnXE6xhDhp8Hcyky018hk"
playerInitTime={0}
metadataVideoTitle="Cap Demo"
accentColor="#5C9FFF"
style={{
aspectRatio: "16/9",
width: "100%",
}}
/>
</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

Iframe embed: add basic hardening and perf a11y

Validate https scheme, lazy-load, and set a referrer policy. Consider an allowlist if embedding third-party providers.

-            <iframe
-              src={content.video.iframe.src}
+            <iframe
+              src={content.video.iframe.src}
               title={content.video.iframe.title || "Cap Demo"}
-              frameBorder="0"
-              allowFullScreen
+              frameBorder="0"
+              loading="lazy"
+              referrerPolicy="strict-origin-when-cross-origin"
+              allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+              allowFullScreen
               style={{
                 position: "absolute",
                 top: 0,
                 left: 0,
                 width: "100%",
                 height: "100%",
                 borderRadius: "0.75rem",
               }}
             />

If content isn’t fully trusted, add a runtime guard:

if (!/^https:\/\//.test(content.video.iframe.src)) return null;
🤖 Prompt for AI Agents
In apps/web/components/seo/SeoPageTemplate.tsx around lines 264 to 301, the
iframe embed is missing basic hardening, performance and accessibility measures;
validate the src uses https at runtime (e.g. a regex guard that returns null if
src does not start with "https://"), optionally check src against an allowlist
of trusted providers, add loading="lazy" to defer offscreen iframes, set a
restrictive referrerPolicy (e.g. "strict-origin" or
"no-referrer-when-downgrade"), and include an appropriate sandbox attribute if
the iframe content is untrusted; ensure title remains present for a11y and keep
the iframe styling but only render when the runtime guards pass.

@@ -0,0 +1,6 @@
export { FeaturePage } from "@/components/features/FeaturePage";
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Avoid lib → components re-export to keep layering clean

Re-exporting a UI component from lib introduces reverse dependency. Prefer importing FeaturePage directly from components or move shared types/transform and the component under a common package (e.g., packages/ui).

Would you like a small patch to move SeoPageContent into lib and keep UI-only exports out of lib?


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"

echo
echo "=== apps/web/lib/features/index.ts ==="
if [ -f "apps/web/lib/features/index.ts" ]; then
  sed -n '1,240p' "apps/web/lib/features/index.ts" || true
else
  echo "NOT FOUND: apps/web/lib/features/index.ts"
fi

echo
echo "=== re-exports that import from @/components ==="
rg -n --hidden --no-ignore-vcs -S '^export\s+\{?.*\}?\s+from\s+["'"'"']@/components' || true

echo
echo "=== all occurrences of @/components ==="
rg -n --hidden --no-ignore-vcs -S '@/components' || true

echo
echo "=== occurrences of SeoPageContent ==="
rg -n --hidden --no-ignore-vcs -S 'SeoPageContent' || true

echo
echo "=== occurrences of FeaturePage ==="
rg -n --hidden --no-ignore-vcs -S 'FeaturePage' || true

echo
echo "=== exports under apps/web/lib ==="
if [ -d "apps/web/lib" ]; then
  rg -n --hidden --no-ignore-vcs -S '^export ' apps/web/lib || true
else
  echo "NOT FOUND: apps/web/lib directory"
fi

echo
echo "=== tsconfig path mappings (tsconfig*.json) ==="
for f in tsconfig.json tsconfig.base.json tsconfig.*.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,240p' "$f"
  fi
done

echo
echo "=== look for packages/ui or shared ui folders ==="
rg -n --hidden --no-ignore-vcs -S 'packages/ui|shared/ui|packages/.*/ui' || true

Length of output: 25342


Remove lib → components re-export; make lib UI-agnostic

apps/web/lib currently re-exports a UI component and imports UI types/pages, creating a reverse dependency — remove the export and move UI-only types/pages into a UI package or into lib so lib does not import from components.

  • apps/web/lib/features/index.ts — remove: export { FeaturePage } from "@/components/features/FeaturePage";
  • apps/web/lib/features/transform.ts — stop importing SeoPageContent from "@/components/seo/types"; move SeoPageContent to lib or packages/ui and import from there.
  • apps/web/lib/seo-pages.ts — stop importing pages/types from "@/components/pages/seo/*"; relocate content/types to lib or packages/ui.

Import FeaturePage directly from "@/components/features/FeaturePage" where needed or move FeaturePage into packages/ui if it must be shared.

🤖 Prompt for AI Agents
In apps/web/lib/features/index.ts around line 1, remove the re-export "export {
FeaturePage } from '@/components/features/FeaturePage';" so the lib package does
not depend on UI; then in apps/web/lib/features/transform.ts stop importing
SeoPageContent from "@/components/seo/types" — move the SeoPageContent type to
lib (or packages/ui) and update the import to that new location; likewise update
apps/web/lib/seo-pages.ts to stop importing pages/types from
"@/components/pages/seo/*" by relocating those types/content into lib or
packages/ui and importing from there; finally, wherever FeaturePage is used,
import it directly from "@/components/features/FeaturePage" or move FeaturePage
into packages/ui if it must be shared.

@@ -0,0 +1,93 @@
import React from "react";
import type { SeoPageContent } from "@/components/seo/types";
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Decouple lib from components

Importing SeoPageContent from components creates a lib → components dependency. Move SeoPageContent to lib (or a shared package) and consume it from both places.

I can prepare a PR-ready patch moving the type and updating imports.

🤖 Prompt for AI Agents
In apps/web/lib/features/transform.ts around line 2, the file imports
SeoPageContent from the components folder creating a lib → components
dependency; move the SeoPageContent type into a shared lib (e.g.,
apps/web/lib/types or a new lib/shared/types file), export it from that new
location, update this file to import SeoPageContent from the new lib path, and
update the components that currently import it to consume the type from the new
shared location; ensure any index barrel files are adjusted and run a repo-wide
replace of the old import path to the new one so TypeScript references are
resolved.

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.

2 participants