Skip to content

feat: /compare page — Recoup vs agencies, teams, and generic AI#49

Open
sidneyswift wants to merge 1 commit into
mainfrom
feat/compare-page-v2
Open

feat: /compare page — Recoup vs agencies, teams, and generic AI#49
sidneyswift wants to merge 1 commit into
mainfrom
feat/compare-page-v2

Conversation

@sidneyswift

@sidneyswift sidneyswift commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Competitive comparison page for the sales funnel.

What

  • Comparison grid: 7 capabilities (content, research, strategy, analytics, valuation, reporting, plugins) across 4 alternatives with visual support indicators
  • Cost comparison: Real numbers from Rostrum, Fat Beats, and Parlophone case studies
  • ChatGPT objection handler: Addresses the feat: GTM v1 scaffold — website, blog, content system, CRM, SEO #1 prospect objection head-on
  • CTA: Links to valuation tool and pricing

Why

Every sales conversation includes 'why not just use ChatGPT?' This page answers at scale. Added to main nav between Results and Pricing.

Pattern

Copy in lib/copy/compare.ts, page in app/compare/page.tsx. Matches existing design system.


Summary by cubic

Add a new /compare page that shows how Recoup stacks up against in-house teams, agencies, and generic AI to handle the “why not ChatGPT?” objection and improve conversions. Linked in the main nav and footer for easy discovery.

  • New Features
    • Responsive comparison grid with 7 capabilities across Recoup, In‑House Team, Creative Agency, and ChatGPT/Claude, with visual support indicators.
    • Cost comparison using real numbers from Rostrum, Fat Beats, and Parlophone case studies.
    • Objection handler that explains where generic AI falls short, with clear points and a concise takeaway.
    • CTAs to the valuation tool and pricing, plus SEO metadata for the page.
    • Centralized copy in lib/copy/compare.ts; page at app/compare/page.tsx; added “Compare” to header and footer nav.

Written for commit b3d5694. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a new “Why Recoup?” comparison page showcasing capabilities, pricing, savings, and alternatives.
    • Added responsive comparison views for desktop and mobile.
    • Added calls to action linking to valuation and pricing information.
    • Added “Compare” links to the main navigation and footer resources.

… teams, generic AI)

- Comparison grid: 7 capabilities across 4 alternatives with support indicators
- Real cost savings section using Rostrum, Fat Beats, Parlophone data
- ChatGPT objection handler section
- Copy in lib/copy/compare.ts (single source of truth pattern)
- Added Compare to header nav + footer resources
- Responsive, matches existing design system
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
marketing Ready Ready Preview Jul 15, 2026 8:55pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new SEO-enabled “Why Recoup?” comparison page with responsive capability and cost comparisons, ChatGPT objection messaging, CTA links, centralized typed copy, and header/footer navigation entries.

Changes

Compare Page

Layer / File(s) Summary
Comparison content model
lib/copy/compare.ts
Defines typed support, comparison, and cost structures and provides the page copy, objection points, and CTA destinations.
Compare page rendering
app/compare/page.tsx, lib/nav.ts
Renders the responsive comparison page, cost cards, objection section, SEO metadata, CTAs, and header/footer navigation links.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new /compare page comparing Recoup against agencies, teams, and generic AI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/compare-page-v2
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/compare-page-v2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/compare/page.tsx (2)

74-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract repeated array to a constant.

As per coding guidelines, follow the DRY principle and avoid code duplication. The array ["recoup", "humans", "agency", "genericAI"] as const is defined inline here and duplicated again at line 92. Consider extracting it to a constant outside the component.

♻️ Proposed fix

Define the constant outside the component:

const COMPETITORS = ["recoup", "humans", "agency", "genericAI"] as const;

And update the usages here and at line 92:

-                {(["recoup", "humans", "agency", "genericAI"] as const).map(
+                {COMPETITORS.map(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/compare/page.tsx` around lines 74 - 75, Extract the repeated competitor
array into a module-level COMPETITORS constant, preserving its readonly tuple
typing, then replace both inline array usages in the component with that
constant.

Source: Coding guidelines


15-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving SupportIcon to its own file.

As per coding guidelines, the codebase strictly follows the Single Responsibility Principle mandating "one function per file". Although this is a small local helper component, extracting it into a separate file (e.g., components/SupportIcon.tsx) would fully align with this rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/compare/page.tsx` around lines 15 - 34, Extract the SupportIcon component
into a dedicated SupportIcon file, preserving its SupportLevel prop type, icon
selection, and existing styling. Update the compare page imports and usages to
reference the extracted component, removing the local SupportIcon definition.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/copy/compare.ts`:
- Around line 24-29: The hardcoded brand and overlong SEO title must use
siteConfig.name and meet the 50–60 character limit. In lib/copy/compare.ts lines
24-29, import siteConfig from lib/config.ts and replace every “Recoup” brand
reference in compareCopy with siteConfig.name; in app/compare/page.tsx lines
8-13, replace metadata brand references likewise and shorten the title to a
unique 50–60 character value.

---

Nitpick comments:
In `@app/compare/page.tsx`:
- Around line 74-75: Extract the repeated competitor array into a module-level
COMPETITORS constant, preserving its readonly tuple typing, then replace both
inline array usages in the component with that constant.
- Around line 15-34: Extract the SupportIcon component into a dedicated
SupportIcon file, preserving its SupportLevel prop type, icon selection, and
existing styling. Update the compare page imports and usages to reference the
extracted component, removing the local SupportIcon definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ad8b7ec5-667c-4639-b619-1f93db322318

📥 Commits

Reviewing files that changed from the base of the PR and between d006b91 and b3d5694.

📒 Files selected for processing (3)
  • app/compare/page.tsx
  • lib/copy/compare.ts
  • lib/nav.ts

Comment thread lib/copy/compare.ts
Comment on lines +24 to +29
export const compareCopy = {
title: "Why Recoup?",
description:
"Generic AI doesn't understand music. Agencies are slow and expensive. Recoup is purpose-built music intelligence — agents that know your artists, your data, and your industry.",

columns: ["Recoup", "In-House Team", "Creative Agency", "ChatGPT / Claude"] as const,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Avoid hardcoding brand values and ensure SEO title limits.

As per coding guidelines, brand values should never be hardcoded; they must be imported from lib/config.ts (using siteConfig.name). Additionally, every page must have a unique SEO title between 50-60 characters, but the current title in app/compare/page.tsx exceeds this limit.

  • lib/copy/compare.ts#L24-L29: Replace instances of the hardcoded "Recoup" brand name with ${siteConfig.name} throughout the exported compareCopy object.
  • app/compare/page.tsx#L8-L13: Replace the hardcoded "Recoup" mentions in the metadata with ${siteConfig.name}, and shorten the SEO title to fit within 50-60 characters (e.g., Why ${siteConfig.name}? — Compare AI Music Agents vs Agencies).
📍 Affects 2 files
  • lib/copy/compare.ts#L24-L29 (this comment)
  • app/compare/page.tsx#L8-L13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/copy/compare.ts` around lines 24 - 29, The hardcoded brand and overlong
SEO title must use siteConfig.name and meet the 50–60 character limit. In
lib/copy/compare.ts lines 24-29, import siteConfig from lib/config.ts and
replace every “Recoup” brand reference in compareCopy with siteConfig.name; in
app/compare/page.tsx lines 8-13, replace metadata brand references likewise and
shorten the title to a unique 50–60 character value.

Source: Coding guidelines

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/compare/page.tsx">

<violation number="1" location="app/compare/page.tsx:9">
P2: The metadata title will render with "Recoup" appearing twice in the browser tab. The string already includes `| Recoup` at the end, and the root layout's `title.template: "%s | Recoup"` applies another suffix on top. The fix: remove `| \${siteConfig.name}` from the title string and let the root layout template handle the suffix, or switch to `title: { absolute: "..." }` if you want full control. Since every page in the codebase follows this same pattern, you may want to fix all at once to keep titles consistent.</violation>

<violation number="2" location="app/compare/page.tsx:9">
P3: Title resolves to ~73 characters, exceeding the recommended 50-60 char range from the SEO guidelines. Truncated titles get cut off in SERP snippets. Shorten the title to stay within 60 chars — e.g. drop "& Agencies" or shorten "Generic AI" since the description already names each alternative.</violation>

<violation number="3" location="app/compare/page.tsx:98">
P3: Column labels in the mobile view are resolved by positional index (`c.columns[i]`), creating a fragile coupling between the copy's column array order and the hardcoded tuple order. If someone reorders the columns in the copy file, the mobile card labels will silently show the wrong company names. Consider building a key→label map instead so the mobile view can look up by key name rather than index.</violation>
</file>

<file name="lib/copy/compare.ts">

<violation number="1" location="lib/copy/compare.ts:25">
P3: The brand name "Recoup" is hardcoded throughout this copy object (title, description, chatgptDescription, chatgptPunchline, costDescription) rather than being derived from `siteConfig.name`. This is inconsistent with `app/compare/page.tsx` in this same PR which imports `siteConfig` and uses `siteConfig.name` in the metadata. If the brand name ever changes, these references would need manual updates. Consider exporting a function that accepts the site name, or interpolating `siteConfig.name` where applicable.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/compare/page.tsx
import { siteConfig } from "@/lib/config";

export const metadata: Metadata = buildPageMetadata({
title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The metadata title will render with "Recoup" appearing twice in the browser tab. The string already includes | Recoup at the end, and the root layout's title.template: "%s | Recoup" applies another suffix on top. The fix: remove | \${siteConfig.name} from the title string and let the root layout template handle the suffix, or switch to title: { absolute: "..." } if you want full control. Since every page in the codebase follows this same pattern, you may want to fix all at once to keep titles consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/compare/page.tsx, line 9:

<comment>The metadata title will render with "Recoup" appearing twice in the browser tab. The string already includes `| Recoup` at the end, and the root layout's `title.template: "%s | Recoup"` applies another suffix on top. The fix: remove `| \${siteConfig.name}` from the title string and let the root layout template handle the suffix, or switch to `title: { absolute: "..." }` if you want full control. Since every page in the codebase follows this same pattern, you may want to fix all at once to keep titles consistent.</comment>

<file context>
@@ -0,0 +1,194 @@
+import { siteConfig } from "@/lib/config";
+
+export const metadata: Metadata = buildPageMetadata({
+  title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`,
+  description:
+    "See how Recoup's purpose-built music agents compare to hiring staff, creative agencies, and generic AI like ChatGPT. Real cost savings from real labels.",
</file context>

Comment thread app/compare/page.tsx
<SupportIcon level={row[key].level} />
<div>
<div className="text-[10px] uppercase tracking-wider text-neutral-500 mb-0.5">
{c.columns[i]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Column labels in the mobile view are resolved by positional index (c.columns[i]), creating a fragile coupling between the copy's column array order and the hardcoded tuple order. If someone reorders the columns in the copy file, the mobile card labels will silently show the wrong company names. Consider building a key→label map instead so the mobile view can look up by key name rather than index.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/compare/page.tsx, line 98:

<comment>Column labels in the mobile view are resolved by positional index (`c.columns[i]`), creating a fragile coupling between the copy's column array order and the hardcoded tuple order. If someone reorders the columns in the copy file, the mobile card labels will silently show the wrong company names. Consider building a key→label map instead so the mobile view can look up by key name rather than index.</comment>

<file context>
@@ -0,0 +1,194 @@
+                        <SupportIcon level={row[key].level} />
+                        <div>
+                          <div className="text-[10px] uppercase tracking-wider text-neutral-500 mb-0.5">
+                            {c.columns[i]}
+                          </div>
+                          <div className="text-xs text-neutral-400 leading-tight">
</file context>

Comment thread app/compare/page.tsx
import { siteConfig } from "@/lib/config";

export const metadata: Metadata = buildPageMetadata({
title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Title resolves to ~73 characters, exceeding the recommended 50-60 char range from the SEO guidelines. Truncated titles get cut off in SERP snippets. Shorten the title to stay within 60 chars — e.g. drop "& Agencies" or shorten "Generic AI" since the description already names each alternative.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/compare/page.tsx, line 9:

<comment>Title resolves to ~73 characters, exceeding the recommended 50-60 char range from the SEO guidelines. Truncated titles get cut off in SERP snippets. Shorten the title to stay within 60 chars — e.g. drop "& Agencies" or shorten "Generic AI" since the description already names each alternative.</comment>

<file context>
@@ -0,0 +1,194 @@
+import { siteConfig } from "@/lib/config";
+
+export const metadata: Metadata = buildPageMetadata({
+  title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`,
+  description:
+    "See how Recoup's purpose-built music agents compare to hiring staff, creative agencies, and generic AI like ChatGPT. Real cost savings from real labels.",
</file context>

Comment thread lib/copy/compare.ts
}

export const compareCopy = {
title: "Why Recoup?",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The brand name "Recoup" is hardcoded throughout this copy object (title, description, chatgptDescription, chatgptPunchline, costDescription) rather than being derived from siteConfig.name. This is inconsistent with app/compare/page.tsx in this same PR which imports siteConfig and uses siteConfig.name in the metadata. If the brand name ever changes, these references would need manual updates. Consider exporting a function that accepts the site name, or interpolating siteConfig.name where applicable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/copy/compare.ts, line 25:

<comment>The brand name "Recoup" is hardcoded throughout this copy object (title, description, chatgptDescription, chatgptPunchline, costDescription) rather than being derived from `siteConfig.name`. This is inconsistent with `app/compare/page.tsx` in this same PR which imports `siteConfig` and uses `siteConfig.name` in the metadata. If the brand name ever changes, these references would need manual updates. Consider exporting a function that accepts the site name, or interpolating `siteConfig.name` where applicable.</comment>

<file context>
@@ -0,0 +1,129 @@
+}
+
+export const compareCopy = {
+  title: "Why Recoup?",
+  description:
+    "Generic AI doesn't understand music. Agencies are slow and expensive. Recoup is purpose-built music intelligence — agents that know your artists, your data, and your industry.",
</file context>

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.

1 participant