-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Add Instant and Studio Mode feature pages + feature templating system #1028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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.
|
Caution Review failedThe pull request is closed. WalkthroughAdds 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
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)
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Wraps the Recording Modes section in a conditional check for agenciesContent.recordingModes to prevent rendering errors if the data is missing.
There was a problem hiding this 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 hiddenUse 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 a11yInline 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-8focus: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 importListItem is no longer used after the dropdown refactor.
- ListItem,apps/web/components/seo/types.ts (2)
29-36: Safer typing for iframe.srcConstrain 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 flexibilityHardcoding /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 hardcodedCurrent 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 URLsSame 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-controlsImprove 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 constraintsConsider 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/secondaryMatches 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 narrowingIf 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 URLAdding 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 a11yInline 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
showVideois 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 bundleThis 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 a11ySame 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 propSame reasoning as Studio page.
- <FeaturePage - config={instantModeConfig} - customIcons={instantModeIcons} - showVideo={true} - /> + <FeaturePage config={instantModeConfig} customIcons={instantModeIcons} />
15-16: CTA copy consistencyConsider 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 absentCurrent interpolation can produce
. Descriptionwhen 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 iframesToday 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
📒 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.tsxapps/web/lib/features/index.tsapps/web/lib/features/transform.tsapps/web/lib/features/types.tsapps/web/app/(site)/Navbar.tsxapps/web/app/(site)/features/instant-mode/InstantModePage.tsxapps/web/app/(site)/features/studio-mode/StudioModePage.tsxapps/web/components/seo/types.tsapps/web/app/(site)/features/instant-mode/page.tsxapps/web/components/features/FeaturePage.tsxapps/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.tsxapps/web/lib/features/index.tsapps/web/lib/features/transform.tsapps/web/lib/features/types.tsapps/web/app/(site)/Navbar.tsxapps/web/app/(site)/features/instant-mode/InstantModePage.tsxapps/web/app/(site)/features/studio-mode/StudioModePage.tsxapps/web/components/seo/types.tsapps/web/app/(site)/features/instant-mode/page.tsxapps/web/components/features/FeaturePage.tsxapps/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.tsxapps/web/lib/features/index.tsapps/web/lib/features/transform.tsapps/web/lib/features/types.tsapps/web/app/(site)/Navbar.tsxapps/web/app/(site)/features/instant-mode/InstantModePage.tsxapps/web/app/(site)/features/studio-mode/StudioModePage.tsxapps/web/components/seo/types.tsapps/web/app/(site)/features/instant-mode/page.tsxapps/web/components/features/FeaturePage.tsxapps/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.tsxapps/web/app/(site)/Navbar.tsxapps/web/app/(site)/features/instant-mode/InstantModePage.tsxapps/web/app/(site)/features/studio-mode/StudioModePage.tsxapps/web/app/(site)/features/instant-mode/page.tsxapps/web/components/features/FeaturePage.tsxapps/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 — LGTMOrder change reads fine and keeps URLs stable.
118-119: Confirmed — repo targets React 19 + Next 14; use() appears in client componentsapps/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 — LGTMapps/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 — LGTMapps/web/app/(site)/features/studio-mode/page.tsx (1)
34-36: Page export — LGTMapps/web/app/(site)/features/instant-mode/page.tsx (1)
34-36: LGTMServer 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.
| See Studio Mode in Action | ||
| </h2> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 ActionOptionally 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.
| 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.
| {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> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"; | |||
There was a problem hiding this comment.
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' || trueLength 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"; | |||
There was a problem hiding this comment.
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.
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
Enhancements
Bug Fixes