Skip to content

feat: detailed case study pages under /results/[slug]#41

Open
sidneyswift wants to merge 2 commits into
mainfrom
feat/case-study-pages
Open

feat: detailed case study pages under /results/[slug]#41
sidneyswift wants to merge 2 commits into
mainfrom
feat/case-study-pages

Conversation

@sidneyswift

@sidneyswift sidneyswift commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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 automated

Why

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

  • Anonymized until customer approval (approved flag in data — flip to true + add real names when sign-off received)
  • Deep Dives section added to /results index page linking to all 3
  • SEO metadata, static params, next-study navigation
  • Same design system: reveal-on-scroll, #0a0a0a bg, shadow borders, Recoup typography

Pre-existing

TS build fails on missing artist-intelligence demo 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.

  • New Features
    • 3 enterprise case studies at: /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 an approved flag).
    • SEO metadata and static generation via generateStaticParams; OG titles/descriptions per study.
    • “Deep Dives” section on /results linking to all case studies.
    • New /plugins page showcasing 4 plugins with capabilities, copy-to-clipboard install commands, compatibility badges, “How it works”, and CTAs; added “Plugins” to header and footer nav.
    • Copy centralized in lib/copy/case-studies.ts and lib/copy/plugins.ts for single-source content.

Written for commit 19e1367. Summary will update on new commits.

Review in cubic

Cosmo added 2 commits July 5, 2026 20:55
- 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
@vercel

vercel Bot commented Jul 6, 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 6, 2026 1:12am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sidneyswift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abdb8706-aa18-44bb-8ed8-ea64046b2d26

📥 Commits

Reviewing files that changed from the base of the PR and between f9862ee and 19e1367.

📒 Files selected for processing (9)
  • app/plugins/layout.tsx
  • app/plugins/page.tsx
  • app/results/[slug]/layout.tsx
  • app/results/[slug]/page.tsx
  • app/results/page.tsx
  • lib/copy/case-studies.ts
  • lib/copy/plugins.ts
  • lib/copy/results.ts
  • lib/nav.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/case-study-pages

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.

@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.

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

Comment thread lib/copy/plugins.ts
name: "Platform",
description:
"Onboarding and diagnostics. Your agent's starting point — validates credentials, discovers capabilities, and runs health checks.",
skillCount: 2,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread lib/copy/plugins.ts
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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread app/plugins/page.tsx
return (
<button
onClick={() => {
navigator.clipboard.writeText(text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread lib/copy/case-studies.ts
export const caseStudies: CaseStudyDetail[] = [
{
slug: "content-agency-replacement",
approved: false,

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 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() {

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: 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>

Comment thread app/plugins/layout.tsx
import { buildPageMetadata } from "@/lib/seo";

export const metadata: Metadata = buildPageMetadata({
title: "Agent Plugins for Music | 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.

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 {

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: 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";

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: 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);

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: 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>

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