feat: /compare page — Recoup vs agencies, teams, and generic AI#49
feat: /compare page — Recoup vs agencies, teams, and generic AI#49sidneyswift wants to merge 1 commit into
Conversation
… 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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds 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. ChangesCompare Page
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/compare/page.tsx (2)
74-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract repeated array to a constant.
As per coding guidelines, follow the DRY principle and avoid code duplication. The array
["recoup", "humans", "agency", "genericAI"] as constis 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 valueConsider moving
SupportIconto 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
📒 Files selected for processing (3)
app/compare/page.tsxlib/copy/compare.tslib/nav.ts
| 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, |
There was a problem hiding this comment.
📐 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 exportedcompareCopyobject.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
There was a problem hiding this comment.
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
| import { siteConfig } from "@/lib/config"; | ||
|
|
||
| export const metadata: Metadata = buildPageMetadata({ | ||
| title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`, |
There was a problem hiding this comment.
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>
| <SupportIcon level={row[key].level} /> | ||
| <div> | ||
| <div className="text-[10px] uppercase tracking-wider text-neutral-500 mb-0.5"> | ||
| {c.columns[i]} |
There was a problem hiding this comment.
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>
| import { siteConfig } from "@/lib/config"; | ||
|
|
||
| export const metadata: Metadata = buildPageMetadata({ | ||
| title: `Why Recoup? — Compare AI Music Agents vs Agencies & Generic AI | ${siteConfig.name}`, |
There was a problem hiding this comment.
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>
| } | ||
|
|
||
| export const compareCopy = { | ||
| title: "Why Recoup?", |
There was a problem hiding this comment.
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>
Competitive comparison page for the sales funnel.
What
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 inapp/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.
lib/copy/compare.ts; page atapp/compare/page.tsx; added “Compare” to header and footer nav.Written for commit b3d5694. Summary will update on new commits.
Summary by CodeRabbit