feat: detailed case study pages under /results/[slug]#41
Conversation
- New /plugins page showcasing all 4 Recoup plugins with install commands - Plugin cards with capabilities, skill counts, audience, GitHub links - Copy-to-clipboard install commands for Claude Code/Cowork/Codex/Cursor - How it works section (Install → Connect → Go) - CTA to docs and pricing - Added Plugins to header nav and footer product section - Copy extracted to lib/copy/plugins.ts (single source of truth pattern)
- 3 enterprise case studies: content-agency-replacement, creative-director-replacement, reporting-automation - Challenge/Solution/Results format with before/after comparison tables - Customer quotes, unlock sections, next-study navigation - Anonymized until customer approval (approved flag in data) - Deep Dives section added to /results index linking to detail pages - SEO metadata with generateStaticParams for static generation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches🧪 Generate unit tests (beta)
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.
9 issues found across 9 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="lib/copy/plugins.ts">
<violation number="1" location="lib/copy/plugins.ts:64">
P1: `skillCount: 17` doesn't match the 8 capabilities actually listed in the array. Either update the count to match what's defined or add the missing capabilities.</violation>
<violation number="2" location="lib/copy/plugins.ts:85">
P1: `skillCount: 2` doesn't match the 4 capabilities actually listed for the Platform plugin. Either update the count or list only 2 capabilities.</violation>
</file>
<file name="lib/copy/case-studies.ts">
<violation number="1" location="lib/copy/case-studies.ts:45">
P2: The `reporting-automation` case study names "Warner Music Group" directly in `tag`, `name`, `ogDescription`, and the hero subheadline, but has `approved: false`. The other two case studies are properly anonymized per the file header policy. Either flip `approved: true` if WMG has signed off, or anonymize the identifying references to match the pattern of the other entries. Publishing identifying customer data without an approval flag risks exposure before sign-off.</violation>
</file>
<file name="app/results/[slug]/layout.tsx">
<violation number="1" location="app/results/[slug]/layout.tsx:4">
P2: Unknown slugs under `/results/[slug]` are dynamically rendered instead of returning 404, which means a typo or stale link will produce a 200 response with no meta description and a "not found" message. Set `export const dynamicParams = false;` in this layout so that Next.js returns a proper 404 for any slug not in the 3 static case studies — this avoids phantom pages being indexed and keeps the behavior consistent with a static content set.</violation>
<violation number="2" location="app/results/[slug]/layout.tsx:14">
P3: `getCaseStudy(slug)` is already exported from `@/lib/copy/case-studies` (line 312) and does the same `caseStudies.find(...)` — using it here instead of the inline version keeps the lookup logic in one place.</violation>
<violation number="3" location="app/results/[slug]/layout.tsx:16">
P2: These case study pages are intended as shareable sales URLs that Sid drops in prospect emails and Slack. Missing a `canonical` URL means the same page content could be treated as separate pages by search engines depending on query params, UTM tags, trailing slashes, or how the link is shared. Adding `alternates: { canonical }` tells search engines which URL is the authoritative one, consolidating ranking signals onto the correct path. `lib/config.ts` already exports `siteConfig.url` for this purpose.</violation>
</file>
<file name="app/plugins/layout.tsx">
<violation number="1" location="app/plugins/layout.tsx:5">
P2: Metadata title includes the `| Recoup` suffix that will double up with the root layout's title template `%s | Recoup`. The page `<title>` will render as `"Agent Plugins for Music | Recoup | Recoup"`. Remove the redundant suffix from the title string so the template produces `"Agent Plugins for Music | Recoup"`.</violation>
</file>
<file name="app/plugins/page.tsx">
<violation number="1" location="app/plugins/page.tsx:42">
P1: `navigator.clipboard.writeText()` returns a Promise that is left unhandled. If the clipboard API fails (e.g., page served over HTTP, user denies permission), the rejection goes unhandled AND the `setCopied(true)` still fires optimistically — so the user sees a green check animation even when nothing was copied. Wrap with async/await + try/catch, and only show the check mark after the promise resolves. If it rejects, either show a brief error state or simply stay on the copy icon.</violation>
</file>
<file name="app/results/[slug]/page.tsx">
<violation number="1" location="app/results/[slug]/page.tsx:10">
P2: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
This page component is 311 lines long, which makes it harder to scan and maintain than the ~100-line guideline. It also bundles `useReveal`, `HeroStat`, and `SolutionStep` alongside the main `CaseStudyPage`, so nothing in the file is easily reused elsewhere. Notably, the `useReveal` hook is already duplicated in `app/results/page.tsx` with identical logic, so extracting it to a shared location would remove duplication as well as shrink this file. Suggest splitting the hook and child components into separate files under `hooks/` and `components/` so each module has a single responsibility.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| name: "Platform", | ||
| description: | ||
| "Onboarding and diagnostics. Your agent's starting point — validates credentials, discovers capabilities, and runs health checks.", | ||
| skillCount: 2, |
There was a problem hiding this comment.
P1: skillCount: 2 doesn't match the 4 capabilities actually listed for the Platform plugin. Either update the count or list only 2 capabilities.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/copy/plugins.ts, line 85:
<comment>`skillCount: 2` doesn't match the 4 capabilities actually listed for the Platform plugin. Either update the count or list only 2 capabilities.</comment>
<file context>
@@ -0,0 +1,125 @@
+ name: "Platform",
+ description:
+ "Onboarding and diagnostics. Your agent's starting point — validates credentials, discovers capabilities, and runs health checks.",
+ skillCount: 2,
+ capabilities: [
+ "Guided onboarding flow",
</file context>
| name: "Catalog Deals", | ||
| description: | ||
| "Turn a messy seller data room into a source-cited deal package. Royalty normalization, rights review, valuation analysis, financing underwriting, and IC memos — all agent-driven.", | ||
| skillCount: 17, |
There was a problem hiding this comment.
P1: skillCount: 17 doesn't match the 8 capabilities actually listed in the array. Either update the count to match what's defined or add the missing capabilities.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/copy/plugins.ts, line 64:
<comment>`skillCount: 17` doesn't match the 8 capabilities actually listed in the array. Either update the count to match what's defined or add the missing capabilities.</comment>
<file context>
@@ -0,0 +1,125 @@
+ name: "Catalog Deals",
+ description:
+ "Turn a messy seller data room into a source-cited deal package. Royalty normalization, rights review, valuation analysis, financing underwriting, and IC memos — all agent-driven.",
+ skillCount: 17,
+ capabilities: [
+ "Data room ingestion & normalization",
</file context>
| return ( | ||
| <button | ||
| onClick={() => { | ||
| navigator.clipboard.writeText(text); |
There was a problem hiding this comment.
P1: navigator.clipboard.writeText() returns a Promise that is left unhandled. If the clipboard API fails (e.g., page served over HTTP, user denies permission), the rejection goes unhandled AND the setCopied(true) still fires optimistically — so the user sees a green check animation even when nothing was copied. Wrap with async/await + try/catch, and only show the check mark after the promise resolves. If it rejects, either show a brief error state or simply stay on the copy icon.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/plugins/page.tsx, line 42:
<comment>`navigator.clipboard.writeText()` returns a Promise that is left unhandled. If the clipboard API fails (e.g., page served over HTTP, user denies permission), the rejection goes unhandled AND the `setCopied(true)` still fires optimistically — so the user sees a green check animation even when nothing was copied. Wrap with async/await + try/catch, and only show the check mark after the promise resolves. If it rejects, either show a brief error state or simply stay on the copy icon.</comment>
<file context>
@@ -0,0 +1,234 @@
+ return (
+ <button
+ onClick={() => {
+ navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
</file context>
| export const caseStudies: CaseStudyDetail[] = [ | ||
| { | ||
| slug: "content-agency-replacement", | ||
| approved: false, |
There was a problem hiding this comment.
P2: The reporting-automation case study names "Warner Music Group" directly in tag, name, ogDescription, and the hero subheadline, but has approved: false. The other two case studies are properly anonymized per the file header policy. Either flip approved: true if WMG has signed off, or anonymize the identifying references to match the pattern of the other entries. Publishing identifying customer data without an approval flag risks exposure before sign-off.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/copy/case-studies.ts, line 45:
<comment>The `reporting-automation` case study names "Warner Music Group" directly in `tag`, `name`, `ogDescription`, and the hero subheadline, but has `approved: false`. The other two case studies are properly anonymized per the file header policy. Either flip `approved: true` if WMG has signed off, or anonymize the identifying references to match the pattern of the other entries. Publishing identifying customer data without an approval flag risks exposure before sign-off.</comment>
<file context>
@@ -0,0 +1,321 @@
+export const caseStudies: CaseStudyDetail[] = [
+ {
+ slug: "content-agency-replacement",
+ approved: false,
+ tag: "Independent Label",
+ name: "Major Independent Label",
</file context>
| import { Metadata } from "next"; | ||
| import { caseStudies } from "@/lib/copy/case-studies"; | ||
|
|
||
| export async function generateStaticParams() { |
There was a problem hiding this comment.
P2: Unknown slugs under /results/[slug] are dynamically rendered instead of returning 404, which means a typo or stale link will produce a 200 response with no meta description and a "not found" message. Set export const dynamicParams = false; in this layout so that Next.js returns a proper 404 for any slug not in the 3 static case studies — this avoids phantom pages being indexed and keeps the behavior consistent with a static content set.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/results/[slug]/layout.tsx, line 4:
<comment>Unknown slugs under `/results/[slug]` are dynamically rendered instead of returning 404, which means a typo or stale link will produce a 200 response with no meta description and a "not found" message. Set `export const dynamicParams = false;` in this layout so that Next.js returns a proper 404 for any slug not in the 3 static case studies — this avoids phantom pages being indexed and keeps the behavior consistent with a static content set.</comment>
<file context>
@@ -0,0 +1,32 @@
+import { Metadata } from "next";
+import { caseStudies } from "@/lib/copy/case-studies";
+
+export async function generateStaticParams() {
+ return caseStudies.map((cs) => ({ slug: cs.slug }));
+}
</file context>
| import { buildPageMetadata } from "@/lib/seo"; | ||
|
|
||
| export const metadata: Metadata = buildPageMetadata({ | ||
| title: "Agent Plugins for Music | Recoup", |
There was a problem hiding this comment.
P2: Metadata title includes the | Recoup suffix that will double up with the root layout's title template %s | Recoup. The page <title> will render as "Agent Plugins for Music | Recoup | Recoup". Remove the redundant suffix from the title string so the template produces "Agent Plugins for Music | Recoup".
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/plugins/layout.tsx, line 5:
<comment>Metadata title includes the `| Recoup` suffix that will double up with the root layout's title template `%s | Recoup`. The page `<title>` will render as `"Agent Plugins for Music | Recoup | Recoup"`. Remove the redundant suffix from the title string so the template produces `"Agent Plugins for Music | Recoup"`.</comment>
<file context>
@@ -0,0 +1,17 @@
+import { buildPageMetadata } from "@/lib/seo";
+
+export const metadata: Metadata = buildPageMetadata({
+ title: "Agent Plugins for Music | Recoup",
+ description:
+ "Install Recoup plugins into Claude Code, Cowork, Codex, or Cursor. Artist research, content creation, catalog deals, and more — powered by the Recoup API.",
</file context>
| const { slug } = await params; | ||
| const study = caseStudies.find((cs) => cs.slug === slug); | ||
| if (!study) return { title: "Case Study | Recoup" }; | ||
| return { |
There was a problem hiding this comment.
P2: These case study pages are intended as shareable sales URLs that Sid drops in prospect emails and Slack. Missing a canonical URL means the same page content could be treated as separate pages by search engines depending on query params, UTM tags, trailing slashes, or how the link is shared. Adding alternates: { canonical } tells search engines which URL is the authoritative one, consolidating ranking signals onto the correct path. lib/config.ts already exports siteConfig.url for this purpose.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/results/[slug]/layout.tsx, line 16:
<comment>These case study pages are intended as shareable sales URLs that Sid drops in prospect emails and Slack. Missing a `canonical` URL means the same page content could be treated as separate pages by search engines depending on query params, UTM tags, trailing slashes, or how the link is shared. Adding `alternates: { canonical }` tells search engines which URL is the authoritative one, consolidating ranking signals onto the correct path. `lib/config.ts` already exports `siteConfig.url` for this purpose.</comment>
<file context>
@@ -0,0 +1,32 @@
+ const { slug } = await params;
+ const study = caseStudies.find((cs) => cs.slug === slug);
+ if (!study) return { title: "Case Study | Recoup" };
+ return {
+ title: study.ogTitle,
+ description: study.ogDescription,
</file context>
| @@ -0,0 +1,311 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
P2: Custom agent: Enforce Clear Code Style and Maintainability Practices
This page component is 311 lines long, which makes it harder to scan and maintain than the ~100-line guideline. It also bundles useReveal, HeroStat, and SolutionStep alongside the main CaseStudyPage, so nothing in the file is easily reused elsewhere. Notably, the useReveal hook is already duplicated in app/results/page.tsx with identical logic, so extracting it to a shared location would remove duplication as well as shrink this file. Suggest splitting the hook and child components into separate files under hooks/ and components/ so each module has a single responsibility.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/results/[slug]/page.tsx, line 10:
<comment>This page component is 311 lines long, which makes it harder to scan and maintain than the ~100-line guideline. It also bundles `useReveal`, `HeroStat`, and `SolutionStep` alongside the main `CaseStudyPage`, so nothing in the file is easily reused elsewhere. Notably, the `useReveal` hook is already duplicated in `app/results/page.tsx` with identical logic, so extracting it to a shared location would remove duplication as well as shrink this file. Suggest splitting the hook and child components into separate files under `hooks/` and `components/` so each module has a single responsibility.</comment>
<file context>
@@ -0,0 +1,311 @@
+import { getCaseStudy, caseStudies } from "@/lib/copy/case-studies";
+
+/* ── reveal-on-scroll ── */
+function useReveal() {
+ const ref = useRef<HTMLDivElement>(null);
+ const [v, setV] = useState(false);
</file context>
| params: Promise<{ slug: string }>; | ||
| }): Promise<Metadata> { | ||
| const { slug } = await params; | ||
| const study = caseStudies.find((cs) => cs.slug === slug); |
There was a problem hiding this comment.
P3: getCaseStudy(slug) is already exported from @/lib/copy/case-studies (line 312) and does the same caseStudies.find(...) — using it here instead of the inline version keeps the lookup logic in one place.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/results/[slug]/layout.tsx, line 14:
<comment>`getCaseStudy(slug)` is already exported from `@/lib/copy/case-studies` (line 312) and does the same `caseStudies.find(...)` — using it here instead of the inline version keeps the lookup logic in one place.</comment>
<file context>
@@ -0,0 +1,32 @@
+ params: Promise<{ slug: string }>;
+}): Promise<Metadata> {
+ const { slug } = await params;
+ const study = caseStudies.find((cs) => cs.slug === slug);
+ if (!study) return { title: "Case Study | Recoup" };
+ return {
</file context>
What
3 detailed enterprise case study pages at:
/results/content-agency-replacement— $5K/mo content agency eliminated/results/creative-director-replacement— $10K/mo creative director replaced in 72hrs/results/reporting-automation— 40+ hrs/mo of manual reporting automatedWhy
The 90-day revenue plan calls for shareable sales URLs. Sid can now drop a specific case study link in prospect emails or Slack. Each page has the full Challenge → Solution → Results breakdown with before/after numbers.
Details
approvedflag in data — flip to true + add real names when sign-off received)#0a0a0abg, shadow borders, Recoup typographyPre-existing
TS build fails on missing
artist-intelligencedemo page (not from this PR).Summary by cubic
Added three detailed case study pages under /results/[slug] and a new /plugins page. This gives sales shareable deep links with quantified results and gives users a simple way to install Recoup plugins.
/results/content-agency-replacement,/results/creative-director-replacement,/results/reporting-automation(Challenge → Solution → Results, before/after tables, quotes, “What this unlocked”, next-study nav; anonymized with anapprovedflag).generateStaticParams; OG titles/descriptions per study./resultslinking to all case studies./pluginspage showcasing 4 plugins with capabilities, copy-to-clipboard install commands, compatibility badges, “How it works”, and CTAs; added “Plugins” to header and footer nav.lib/copy/case-studies.tsandlib/copy/plugins.tsfor single-source content.Written for commit 19e1367. Summary will update on new commits.