Skip to content

feat: homepage valuation hero (Catalog HQ 1/3, chat#1850)#1852

Merged
sweetmantech merged 7 commits into
mainfrom
feat/home-valuation-hero
Jul 7, 2026
Merged

feat: homepage valuation hero (Catalog HQ 1/3, chat#1850)#1852
sweetmantech merged 7 commits into
mainfrom
feat/home-valuation-hero

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What this does

First PR of the Catalog HQ homepage stack (#1850, "Open — chat homepage: Catalog HQ").

For accounts with at least one catalog, the homepage now renders a valuation hero above the chat area: artist name + avatar, the estimated catalog value (display treatment on the mid figure), the low-to-high range, and the measured-track count — the same number the marketing valuation card showed the lead.

  • Data path: existing GET /api/accounts/{accountId}/catalogs (what the Catalogs page uses) → new GET /api/catalogs/measurements?catalogId= (per-ISRC latest measurements + valuation: { low, mid, high }; contract per Valuation-first homepage (Catalog HQ): surface catalog value + task runs on chat home; fix the .dev cross-domain auth regression #1850, implemented in a parallel api PR).
  • Graceful degradation is the core contract: the measurements endpoint is not on prod yet. Endpoint 404/error, error envelope, catalogs failure, or zero catalogs → the homepage renders exactly the current greeting ("Ask me about {artist}") with zero regression. retry: false on the measurements query so a 404 falls back immediately.
  • Composed hooks per the chat hooks conventions: useHomeValuation composes useCatalogs + new useCatalogMeasurements + ArtistProvider; no transport changes. The hero-visibility decision is a pure function (getValuationHeroState) with full unit coverage.
  • hideGreeting is an optional, default-false prop threaded HomePage → NewChatBootstrap → Chat, so every other Chat mount is untouched.
  • Design per DESIGN.md: shadow-as-border card, achromatic chrome, tabular numerals with tight tracking on the dollar figure. Note: the chat app does not currently load Geist Pixel Square (only Plus Jakarta Sans/Geist are wired in app/globals.css), so the figure uses the app's existing heading font at display scale rather than adding a new font in this PR.

Tests (TDD — confirmed RED before implementing)

14 new unit tests, full suite 97 passed (28 files):

  • lib/home/__tests__/getValuationHeroState.test.ts — hero hidden while loading / no catalogs / catalogs failed / measurements failed / no valuation band; shown with band + measured-track count when data is complete (the Done-when).
  • lib/catalog/__tests__/getCatalogMeasurements.test.ts — GET URL + bearer auth, 404 throws, error envelope throws.
  • lib/catalog/__tests__/formatValuationAmount.test.ts$1.4M / $959K / $2M compact formatting.

pnpm exec tsc --noEmit: no new errors (the two pre-existing failures in lib/emails/__tests__/extractSendEmailResults.test.ts and components/VercelChat/tools/SpotifyDeepResearchResult.tsx are on main and untouched). eslint clean on all changed files (next lint itself is broken repo-wide under Next 16).

Screenshots

N/A — the measurements endpoint is not live on prod/preview yet, so the hero cannot render there; the verifiable behavior on preview is the fallback (unchanged greeting), which I verified (see PR comment).

Stack

Part of a 3-PR stack for chat#1850 (merge in order, each PR only after its base):

  1. this PR feat/home-valuation-hero → base main
  2. feat/home-tasks-module → base feat/home-valuation-hero
  3. feat/home-command-bar → base feat/home-tasks-module

After this merges, PR 2 gets re-targeted to main. Note: the tracking issue's PR table says base test, but the repo retired test as a PR target (#1843) — the stack bases on main per current CLAUDE.md.

Closes nothing on its own — tracked in #1850.

🤖 Generated with Claude Code


Summary by cubic

Adds a valuation hero to the homepage for catalog accounts, showing estimated value, range, and measured track count above chat. It renders inside ChatGreeting and falls back to the greeting with a “Get a free catalog valuation” CTA when data isn’t available, aligned with #1850.

  • New Features

    • ChatGreeting now shows ValuationHero when valuation data exists; otherwise it shows the greeting plus a CTA to VALUATION_URL.
    • useHomeValuation composes useCatalogs + useCatalogMeasurements and gates visibility via getValuationHeroState; data comes from GET /api/catalogs/{catalogId}/measurements[?artist_account_id][&limit=1] with compact USD via formatValuationAmount. Any error or missing measured_song_count hides the hero.
  • Refactors

    • Moved the header logic into ChatGreeting; no Chat changes or prop drilling.
    • Artist scoping uses the v2 artist_account_id filter and only renders when the response echoes the same scope; pre‑v2 responses or zero measured_song_count fall back to the greeting. Removed the old findArtistCatalog/isArtistCatalogMatch path.

Written for commit 540a53c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a homepage valuation hero that highlights artist artwork, valuation range, and track count when valuation data is available.
    • Improved homepage chat behavior so the greeting can be hidden when the valuation hero is shown.
    • Added support for compact valuation formatting for clearer display values.
  • Bug Fixes

    • Made homepage valuation content render only when the required catalog and measurement data are available, helping avoid incomplete or incorrect display states.

For accounts with >=1 catalog, the homepage renders the estimated catalog
value (artist, mid figure with display treatment, low-to-high range,
measured-track count) above the chat area, fed by the new
GET /api/catalogs/measurements endpoint via composed hooks
(useCatalogs -> useCatalogMeasurements -> useHomeValuation).

The endpoint is still rolling out: any failure (404, error envelope, no
catalogs) hides the hero and keeps the existing greeting untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 7, 2026 2:09pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 8 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8548fa7e-6715-49ea-94b7-12bf1eaa601a

📥 Commits

Reviewing files that changed from the base of the PR and between 67afc14 and 540a53c.

⛔ Files ignored due to path filters (2)
  • lib/catalog/__tests__/getCatalogMeasurements.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/home/__tests__/getValuationHeroState.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (6)
  • components/Chat/ChatGreeting.tsx
  • hooks/useCatalogMeasurements.ts
  • hooks/useHomeValuation.ts
  • lib/catalog/getCatalogMeasurements.ts
  • lib/consts.ts
  • lib/home/getValuationHeroState.ts
📝 Walkthrough

Walkthrough

Adds a homepage valuation feature: new API function and types for catalog measurements, useCatalogMeasurements and useHomeValuation hooks, getValuationHeroState logic, and a ValuationHero component wired into HomePage. Chat/NewChatBootstrap gain a hideGreeting prop. Several catalog utility files receive formatting-only edits.

Changes

Homepage Valuation Hero Feature

Layer / File(s) Summary
Catalog measurements API
lib/catalog/getCatalogMeasurements.ts
Adds CatalogSongMeasurement, CatalogValuationBand, CatalogMeasurementsResponse types and getCatalogMeasurements fetch function with error handling.
Valuation state and hooks
hooks/useCatalogMeasurements.ts, hooks/useHomeValuation.ts, lib/home/getValuationHeroState.ts
useCatalogMeasurements fetches measurements via React Query; getValuationHeroState derives show/hide and display fields; useHomeValuation composes artist, catalog, and measurement data into HomeValuationState.
ValuationHero UI and homepage wiring
components/Home/ValuationHero.tsx, components/Home/HomePage.tsx, lib/catalog/formatValuationAmount.ts
New ValuationHero component renders artist info, formatted midpoint, and range using formatValuationAmount; HomePage conditionally renders it and passes hideGreeting to NewChatBootstrap.
Chat greeting suppression
components/VercelChat/chat.tsx, components/VercelChat/NewChatBootstrap.tsx
Adds optional hideGreeting prop threaded through NewChatBootstrap and Chat, conditionally hiding ChatGreeting and updating the memo comparator.

Catalog Utility Formatting Cleanups

Layer / File(s) Summary
Trailing comma/line-wrap edits
lib/catalog/createCatalogResult.ts, lib/catalog/createSearchResult.ts, lib/catalog/formatArtists.ts, lib/catalog/getCatalogSongs.ts, lib/catalog/parseCsvFile.ts, lib/catalog/postCatalogSongs.ts, lib/catalog/uploadBatchSongs.ts
Formatting-only changes (trailing commas, multi-line fetch calls) with no logic changes.

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

Sequence Diagram(s)

sequenceDiagram
  participant HomePage
  participant useHomeValuation
  participant useCatalogMeasurements
  participant getCatalogMeasurements
  participant getValuationHeroState
  participant ValuationHero

  HomePage->>useHomeValuation: request valuation state
  useHomeValuation->>useCatalogMeasurements: query(catalogId, artistAccountId)
  useCatalogMeasurements->>getCatalogMeasurements: fetch(catalogId, accessToken)
  getCatalogMeasurements-->>useCatalogMeasurements: CatalogMeasurementsResponse
  useHomeValuation->>getValuationHeroState: derive show/hide + fields
  getValuationHeroState-->>useHomeValuation: ValuationHeroState
  useHomeValuation-->>HomePage: HomeValuationState
  alt show is true
    HomePage->>ValuationHero: render(artistName, valuation, measuredTrackCount)
    HomePage->>HomePage: hideGreeting=true for NewChatBootstrap
  else show is false
    HomePage->>HomePage: render NewChatBootstrap only
  end
Loading

Possibly related issues

Poem

A hero blooms on the homepage stage,
Valuations rendered, band by band, page by page.
Chat quiets its greeting, hushed and neat,
While hooks and states fetch data discreet.
Commas tidied, style refined —
🐰 SOLID rabbits, well-aligned!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning useHomeValuation still picks catalogs?.[0], so multi-catalog accounts can show the wrong hero or hide a valid one; API fetch logic is also duplicated. Resolve the intended catalog explicitly before fetching measurements, and factor shared fetch/error handling into a small helper to reduce duplication.
✅ Passed checks (2 passed)
Check name Status Explanation
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/home-valuation-hero

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 528523bada

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread hooks/useHomeValuation.ts Outdated
const { data: catalogsData, isError: catalogsFailed } = useCatalogs();
const catalogs = catalogsData?.catalogs;
const { data: measurements, isError: measurementsFailed } =
useCatalogMeasurements(catalogs?.[0]?.id);

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 Badge Tie the valuation to the displayed artist

When an account has multiple catalogs and the user has selected an artist that is not represented by catalogs[0], this fetches measurements for the first catalog but the returned hero is labeled with selectedArtist?.name and selectedArtist?.image. That makes the homepage show one catalog's valuation under another artist's name/avatar; choose the catalog that matches the selected artist or label the card with the catalog whose measurements were fetched.

Useful? React with 👍 / 👎.

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

2 issues found across 12 files

Confidence score: 3/5

  • In hooks/useHomeValuation.ts, valuation data appears to be fetched from a source that can drift from the current selectedArtist, so the hero may show the wrong artist identity for the catalog being valued; merging as-is risks user-facing trust issues and incorrect context in valuation flows — align the fetch key/source with the active artist/catalog before merging and verify with a multi-artist account case.
  • In lib/catalog/getCatalogMeasurements.ts, missing try/catch logging breaks the API error-reporting consistency used elsewhere, which can make production failures harder to diagnose even if behavior otherwise matches; add the same console.error + re-throw pattern before merging to preserve observability.
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/catalog/getCatalogMeasurements.ts">

<violation number="1" location="lib/catalog/getCatalogMeasurements.ts:29">
P2: Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.</violation>
</file>

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

Re-trigger cubic

* The endpoint is being rolled out; callers must treat any thrown error as
* "no valuation available" and fall back gracefully.
*/
export async function getCatalogMeasurements(

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: Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.

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

<comment>Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.</comment>

<file context>
@@ -0,0 +1,55 @@
+ * The endpoint is being rolled out; callers must treat any thrown error as
+ * "no valuation available" and fall back gracefully.
+ */
+export async function getCatalogMeasurements(
+  catalogId: string,
+  accessToken: string,
</file context>

Comment thread hooks/useHomeValuation.ts Outdated
@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification (sha 528523b, https://chat-git-feat-home-valuation-hero-recoup.vercel.app/):

  • Vercel preview build: SUCCESS.
  • Loaded the preview homepage in a fresh browser session (signed out). It renders exactly the current experience: 'Ask me about your roster' greeting, chat input, Privy login modal — no hero, no layout shift, zero console errors. This is the graceful-fallback path working as designed.
  • As expected, the hero itself cannot render on this preview: GET /api/catalogs/measurements is not live on prod (it ships in a parallel api PR), and the hero requires it. The hidden-hero fallback is exactly what this deployment should show.
  • Not verified on preview: the authenticated hero-visible state (needs the api endpoint live + an authenticated session; Privy OTP retrieval wasn't available to me in this run). That state is covered by the unit tests on getValuationHeroState and should be re-checked visually once the api PR deploys.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Live preview verification — 2026-07-06 (authenticated pass)

Setup: api test branch synced with main (b059655d) so test-recoup-api.vercel.app serves the new measurements endpoint (live 21:15:48Z); signed into this PR's preview (chat-ghki7iqd0-recoup.vercel.app) via Privy OTP as an account owning a measured catalog.

Done-when criterion Result
Account with catalog + endpoint data → hero shows the dollar figure without touching chat ✅ Hero renders $150.3K · Range $103.2K to $211.4K · 5 tracks measured on load
Values come from the real endpoint GET test-recoup-api.vercel.app/api/catalogs/measurements?catalogId=7d3e5c35… → 200 (observed in the network log); rendered values match the API band to the displayed precision (mid $150,299 / low $103,227 / high $211,410)
No catalog or endpoint failure → unchanged greeting ✅ (verified signed-out earlier — greeting identical, 0 console errors)
Chat input unaffected below the hero ✅ input + model picker render normally under the hero

hero rendering live valuation

(screenshot hosted on the evidence/chat1852-verification branch; safe to delete after merge)

🐛 One real finding — artist/catalog pairing

The hero shows the selected artist (Ana Bárbara, this account's active workspace artist) next to the valuation of a Del Water Gap catalog. The artist label comes from the workspace context while the catalog is just the account's first/only catalog — the two aren't linked. Correct for the target funnel case (fresh valuation lead: one artist, one catalog) but wrong pairing on multi-artist accounts like this one.

Suggested fix (fine as a fast-follow, doesn't block the stack): derive the hero's label from the catalog (catalogs.name, e.g. "Del Water Gap Catalog") instead of the active artist — or match catalog→artist when resolvable and fall back to the catalog name.

Notes for the record

  • Preview auth constraint discovered: chat previews use the test-mode Privy app (cmc52us2g…), which has hit its user cap — brand-new emails get "Unable to sign in / test application has reached its user limit." Existing users sign in fine. Fresh-account preview testing is blocked until that app is upgraded (tracked on chat#1850).
  • The [TEST]-named catalog used here is the one seeded during api#757 verification; delete anytime.

🤖 Generated with Claude Code

…ngs in the catalog before pairing

The hero paired the workspace's selected artist with the account's first
catalog unconditionally, so switching artists kept showing another
catalog's value under the new artist's name (trust-killer on
multi-artist accounts, caught in live preview verification on
#1852).

Now: with no artist selected the hero shows the whole catalog's value
under the catalog name; with an artist selected it resolves the catalog
by name match (findArtistCatalog), verifies the pairing via the existing
GET /api/catalogs/songs artistName filter (total_count > 0, pageSize 1),
and hides while resolving, on zero match, or on check failure — falling
back to the greeting. Switching artists re-resolves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Fix pushed: artist-scoped hero (537fb3f6)

Addresses the artist/catalog mismatch from the live verification above — sweetman confirmed the severity: switching artists kept showing another catalog's value under the new artist's name, an immediate trust-killer for multi-artist accounts.

New behavior:

  • No artist selected → hero shows the whole catalog's value under the catalog name (per sweetman's call, 2026-07-06).
  • Artist selected → the catalog is resolved by name match (findArtistCatalog, marketing's "{Artist} Catalog" convention, first-catalog fallback), then the pairing is verified with data: GET /api/catalogs/songs?catalogId&artistName&pageSize=1total_count > 0 (existing endpoint + filter, no new api surface). Hero renders only for a verified pair; hides while resolving, on zero match, or on check failure — falling back to the greeting. Switching artists re-resolves.
  • Measurements are now read from the matched catalog, not blindly catalogs[0].

Verification: TDD (RED→GREEN) — resolver + new findArtistCatalog at 15 tests; full suite on this branch 105/105; stack rebased (chat#1853c2d0e251, chat#185429ad23f4) and green at the top (122/122); tsc adds 0 errors (8 pre-existing on main). Live authed re-check on the new preview pending (Privy OTP required); the decisive manual case: account with Ana Bárbara selected + a Del Water Gap catalog → no hero (greeting), which was the failing scenario.

🤖 Generated with Claude Code

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

1 issue found across 5 files (changes from recent commits).

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/catalog/getCatalogMeasurements.ts">

<violation number="1" location="lib/catalog/getCatalogMeasurements.ts:29">
P2: Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.</violation>
</file>

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

Re-trigger cubic

Comment thread lib/home/getValuationHeroState.ts Outdated
…tistName filter no-ops on unlinked songs

Live preview verification caught the api-side hole: GET /api/catalogs/songs
with artistName=Ana+Bárbara returned all 6 songs (total_count 6,
artists:[null]) of a catalog she has no songs in — the filter rides the
artists join that api#681 relaxed to a LEFT-join, so unlinked songs pass
any artist filter. The hero therefore now fetches the catalog's songs
unfiltered and matches client-side (isArtistCatalogMatch): catalog named
for the artist, or the artist present in a song's artists array. Null- or
unlinked songs match nobody. api-side filter fix tracked on
#1850.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

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

1 issue found across 5 files (changes from recent commits).

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/catalog/getCatalogMeasurements.ts">

<violation number="1" location="lib/catalog/getCatalogMeasurements.ts:29">
P2: Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread hooks/useHomeValuation.ts Outdated
@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Live verification of the artist-scoping fix — 2026-07-06 (head 7355a775, branch-alias preview)

Signed into chat-git-feat-home-valuation-hero-recoup.vercel.app with the same multi-artist account that exposed the bug. The failing scenario now passes.

Check Before (528523ba) After (7355a775)
Ana Bárbara selected, account owns a Del Water Gap catalog ❌ Hero showed "Ana Bárbara — $150.3K" (another catalog's money) No hero — greeting "Ask me about Ana Bárbara"
Switch artist (→ Apache) ❌ Value stayed put under the new artist ✅ Re-resolves: greeting follows the artist, hero stays hidden
Network path catalogs/songs?…&artistName=… (filter no-ops, total_count: 6 for an artist with 0 songs) catalogs/songs?catalog_id=…&page=1&limit=50 (no filter param) + client-side match — null-linked songs match nobody
Console clean (one pre-existing form-field a11y notice, unrelated)

Ana Bárbara selected — greeting, no hero
Apache selected — greeting follows, no hero

Root cause of the first attempt's failure (for the record): the api's artistName filter on GET /api/catalogs/songs doesn't filter unlinked songs — artistName=Ana+Bárbara returned all 6 songs (artists:[null]) of a catalog she has no songs in. Latent since api#681 relaxed the artists join to a LEFT-join; my first commit (537fb3f6) trusted that filter. The shipped fix (7355a775) matches client-side against catalog name / the songs' artists arrays instead. api-side filter fix logged as an Open item on chat#1850.

Positive case caveat, stated honestly: this account's roster has no artist with songs in its catalog, so the hero-visible path wasn't re-exercised live after the rework — it's covered by the unit matrix (21 lib/home tests incl. object/string artist shapes and the name-convention match) and its rendering path (unchanged ValuationHero) was live-verified earlier today ($150.3K screenshot above). The real funnel case (claimed catalog named "{Artist} Catalog" + artist-linked songs via api#684) satisfies the matcher two ways.

Stack state: 18532874fedd, 1854efd97b87 (rebased on this fix), 128/128 at the top, tsc 0 new errors.

🤖 Generated with Claude Code

… delete the client-side matcher

The hero now passes the selected artist's account_id as
artist_account_id to GET /api/catalogs/measurements (docs#267 contract,
api#763 impl): the api scopes measurements + valuation server-side via
catalog_songs ∩ song_artists over the account's single catalog. The
response echoes the applied filter and the resolver requires the echo to
match before rendering an artist-labeled value — a pre-v2 deployment
ignores the unknown param and the hero hides instead of showing
whole-catalog money under an artist's name (deploy-skew safe).

Deletes findArtistCatalog + isArtistCatalogMatch and their tests: the
name-convention matching and 50-song sampling (which missed artists
deeper in large catalogs) are subsumed by the server-side filter.
Supersedes the per-artist-catalog prototype (rejected 2026-07-06, see
#1850 decision callout).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Reworked to the v2 endpoint filter (67afc145) — client-side matcher deleted

Per the 2026-07-06 decision on chat#1850 (single catalog; artist scoping inside the endpoint), the hero now passes the selected artist's account_id as artist_account_id to GET /api/catalogs/measurements — contract docs#267, implementation api#763 (which also fixes the silent 1,000-row cap). The api scopes measurements + valuation server-side via catalog_songs ∩ song_artists; switching artists changes the value (fixture ground truth: Elvis Crespo $14.6M · Apache $6.0M · September Mourning $173K · whole catalog $132.5M — all exact to the stream on the api#763 preview).

Deploy-skew safety (echo gating): the v2 response echoes the applied filter in artist_account_id; the resolver requires the echo to match the selected artist before rendering an artist-labeled value. A pre-v2 deployment ignores the unknown param and returns whole-catalog numbers without the echo → the hero hides (greeting) instead of showing wrong-scoped money under an artist's name. This is why nothing here can regress even while api#763 is unmerged.

Deleted: lib/home/findArtistCatalog.ts, lib/home/isArtistCatalogMatch.ts + tests — the name-convention matching and 50-song sampling (which missed artists deeper in large catalogs) are subsumed by the server-side filter.

Gates: suite 103/103 on this branch, 120/120 at the top of the restacked chain (chat#18533a596cca, chat#1854a151df63); tsc 0 new errors (8 pre-existing); prettier clean. Merge order: docs#267 → api#763 → this PR (then the stack).

🤖 Generated with Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (5)
hooks/useHomeValuation.ts (1)

7-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

artistName field is misleadingly named-it can hold the catalog name.

HomeValuationState.artistName is documented/typed as if it's always an artist name, but when showArtist is false (lines 52-54) it's populated with state.catalogName instead. A consumer reading the type won't realize this field can represent two different concepts. Renaming to something like displayName (with showArtist already indicating what kind of label it is) would make the contract self-documenting.

♻️ Proposed rename
 export type HomeValuationState =
   | { show: false }
   | {
       show: true;
-      artistName: string;
+      displayName: string;
       artistImage: string;
       valuation: CatalogValuationBand;
       measuredTrackCount: number;
     };
...
   return {
     show: true,
-    artistName: state.showArtist
+    displayName: state.showArtist
       ? selectedArtistName || state.catalogName
       : state.catalogName,
As per coding guidelines, "Ensure code is self-documenting through precise naming with verbs for functions and nouns for variables."

Also applies to: 50-58

🤖 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 `@hooks/useHomeValuation.ts` around lines 7 - 15, The HomeValuationState shape
uses artistName for a value that can be either an artist name or a catalog name,
which is misleading. Rename the field to a neutral, self-documenting name like
displayName in HomeValuationState and in the logic inside useHomeValuation where
state.artistName is assigned from either artistName or catalogName, then update
all consumers to use the new property consistently.

Source: Coding guidelines

lib/catalog/getCatalogMeasurements.ts (1)

60-64: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Unvalidated cast of API response to typed interface.

await response.json() is cast directly to CatalogMeasurementsResponse with no runtime validation. If the API returns a shape that doesn't match (missing valuation, wrong field types), TypeScript won't catch it and the bad data flows into getValuationHeroState/useHomeValuation silently until something downstream throws or renders garbage. Given this is a "trust boundary" for an actively-rolling-out endpoint (per the JSDoc above), a lightweight zod schema/safeParse would catch contract drift at the source rather than relying on ad-hoc optional chaining downstream.

🛡️ Proposed fix using zod
+import { z } from "zod";
+
+const catalogMeasurementsSchema = z.object({
+  status: z.string(),
+  measurements: z.array(
+    z.object({
+      isrc: z.string(),
+      playcount: z.number().nullable().optional(),
+      measured_at: z.string().nullable().optional(),
+    }),
+  ),
+  valuation: z.object({ low: z.number(), mid: z.number(), high: z.number() }),
+  artist_account_id: z.string().nullable().optional(),
+  error: z.string().optional(),
+});
+
   const data: CatalogMeasurementsResponse = await response.json();
+  const parsed = catalogMeasurementsSchema.parse(data);
🤖 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/catalog/getCatalogMeasurements.ts` around lines 60 - 64, The response
from response.json() in getCatalogMeasurements is being trusted without runtime
validation, so add a lightweight schema check before treating it as
CatalogMeasurementsResponse. Use a zod schema or equivalent safeParse near
getCatalogMeasurements to validate the expected shape (including valuation and
status/error fields) and throw a clear error when the API contract drifts,
instead of passing potentially invalid data into getValuationHeroState and
useHomeValuation.

Source: Coding guidelines

components/Home/ValuationHero.tsx (3)

5-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export the props interface.

ValuationHeroProps isn't exported, deviating from the project's naming/export convention for component prop types.

As per coding guidelines, "Export component prop types with explicit ComponentNameProps naming convention."

🔧 Proposed fix
-interface ValuationHeroProps {
+export interface ValuationHeroProps {
🤖 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 `@components/Home/ValuationHero.tsx` around lines 5 - 10, ValuationHeroProps is
defined but not exported, which breaks the component prop-type export
convention. Update the ValuationHeroProps interface in ValuationHero to be
exported so other modules can reference the component props type consistently.
Keep the existing ComponentNameProps naming pattern intact and only adjust the
interface declaration.

Source: Coding guidelines


28-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing alt text on artist avatar.

ImageWithFallback is rendered without an alt attribute, so screen readers get no description of the image. Since artistName is already available in scope, pass it through.

As per coding guidelines, "Provide proper ARIA roles/states and test with screen readers" and "Start with semantic HTML first, then augment with ARIA if needed."

♿ Proposed fix
         <span className="inline-block size-10 shrink-0 overflow-hidden rounded-full ring-1 ring-foreground">
-          <ImageWithFallback src={artistImage} />
+          <ImageWithFallback src={artistImage} alt={artistName} />
         </span>
🤖 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 `@components/Home/ValuationHero.tsx` around lines 28 - 32, The artist avatar
image is missing accessible alternative text in ValuationHero. Update the
ImageWithFallback usage inside the artistImage conditional to pass the available
artistName as the alt value so screen readers can describe the avatar. Keep the
fix localized to the ImageWithFallback component call and preserve the existing
wrapper markup.

Source: Coding guidelines


33-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a semantic heading for the artist name.

The artist name (the section's primary label) is a <span> rather than a heading element, which reduces navigability for screen-reader users who jump between headings.

As per coding guidelines, "Use semantic HTML elements appropriate to the component's role."

🤖 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 `@components/Home/ValuationHero.tsx` around lines 33 - 40, The artist name in
ValuationHero is rendered as a non-semantic span, which should be a heading for
better accessibility and navigation. Update the artistName label in the
ValuationHero component to use an appropriate heading element while preserving
its styling, and keep the surrounding text as the supporting subtitle so screen
readers can jump by section heading.

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 `@hooks/useHomeValuation.ts`:
- Around line 33-37: Resolve the catalog selection in useHomeValuation before
calling useCatalogMeasurements, since catalogs[0] is arbitrary for the
many-to-many account_catalogs relationship. Update the catalog lookup logic to
choose the intended catalog explicitly based on the current account or artist
context instead of array order, and keep the selected catalog value flowing into
useCatalogMeasurements so the correct catalog name/value and artist-scoped
measurement are used.

---

Nitpick comments:
In `@components/Home/ValuationHero.tsx`:
- Around line 5-10: ValuationHeroProps is defined but not exported, which breaks
the component prop-type export convention. Update the ValuationHeroProps
interface in ValuationHero to be exported so other modules can reference the
component props type consistently. Keep the existing ComponentNameProps naming
pattern intact and only adjust the interface declaration.
- Around line 28-32: The artist avatar image is missing accessible alternative
text in ValuationHero. Update the ImageWithFallback usage inside the artistImage
conditional to pass the available artistName as the alt value so screen readers
can describe the avatar. Keep the fix localized to the ImageWithFallback
component call and preserve the existing wrapper markup.
- Around line 33-40: The artist name in ValuationHero is rendered as a
non-semantic span, which should be a heading for better accessibility and
navigation. Update the artistName label in the ValuationHero component to use an
appropriate heading element while preserving its styling, and keep the
surrounding text as the supporting subtitle so screen readers can jump by
section heading.

In `@hooks/useHomeValuation.ts`:
- Around line 7-15: The HomeValuationState shape uses artistName for a value
that can be either an artist name or a catalog name, which is misleading. Rename
the field to a neutral, self-documenting name like displayName in
HomeValuationState and in the logic inside useHomeValuation where
state.artistName is assigned from either artistName or catalogName, then update
all consumers to use the new property consistently.

In `@lib/catalog/getCatalogMeasurements.ts`:
- Around line 60-64: The response from response.json() in getCatalogMeasurements
is being trusted without runtime validation, so add a lightweight schema check
before treating it as CatalogMeasurementsResponse. Use a zod schema or
equivalent safeParse near getCatalogMeasurements to validate the expected shape
(including valuation and status/error fields) and throw a clear error when the
API contract drifts, instead of passing potentially invalid data into
getValuationHeroState and useHomeValuation.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be40aaba-5e06-4eec-ad15-6a2d1d21b790

📥 Commits

Reviewing files that changed from the base of the PR and between a58a467 and 67afc14.

⛔ Files ignored due to path filters (3)
  • lib/catalog/__tests__/formatValuationAmount.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/catalog/__tests__/getCatalogMeasurements.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/home/__tests__/getValuationHeroState.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (16)
  • components/Home/HomePage.tsx
  • components/Home/ValuationHero.tsx
  • components/VercelChat/NewChatBootstrap.tsx
  • components/VercelChat/chat.tsx
  • hooks/useCatalogMeasurements.ts
  • hooks/useHomeValuation.ts
  • lib/catalog/createCatalogResult.ts
  • lib/catalog/createSearchResult.ts
  • lib/catalog/formatArtists.ts
  • lib/catalog/formatValuationAmount.ts
  • lib/catalog/getCatalogMeasurements.ts
  • lib/catalog/getCatalogSongs.ts
  • lib/catalog/parseCsvFile.ts
  • lib/catalog/postCatalogSongs.ts
  • lib/catalog/uploadBatchSongs.ts
  • lib/home/getValuationHeroState.ts

Comment thread hooks/useHomeValuation.ts Outdated
Comment on lines +33 to +37
const { data: catalogsData, isError: catalogsFailed } = useCatalogs();
const catalog = catalogsData?.catalogs?.[0];

const { data: measurements, isError: measurementsFailed } =
useCatalogMeasurements(catalog?.id, selectedArtistAccountId ?? undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether accounts can have multiple catalogs and how catalog/artist ownership is modeled.
ast-grep outline hooks/useCatalogs.ts
rg -n -A5 -B5 'catalogs' hooks/useCatalogs.ts
rg -n 'interface Catalog' -A10 types/Catalog.ts

Repository: recoupable/chat

Length of output: 778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the home valuation hook and measurement hook signatures.
ast-grep outline hooks/useHomeValuation.ts
ast-grep outline hooks/useCatalogMeasurements.ts
printf '\n--- useHomeValuation.ts ---\n'
cat -n hooks/useHomeValuation.ts | sed -n '1,220p'
printf '\n--- useCatalogMeasurements.ts ---\n'
cat -n hooks/useCatalogMeasurements.ts | sed -n '1,220p'

Repository: recoupable/chat

Length of output: 4414


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find catalog-related data model and any code that treats the first catalog specially.
rg -n "catalogs\\?\\[0\\]|catalogsData\\.catalogs|selectedArtistAccountId|artist_account_id|catalog_id" hooks src . --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: recoupable/chat

Length of output: 8901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the API layer for catalogs and measurements to understand ownership/filtering.
rg -n -A4 -B4 "getCatalogs|getCatalogMeasurements|CatalogsResponse|MeasurementsResponse" . --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: recoupable/chat

Length of output: 16726


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the valuation state logic and its tests for any contract about catalog selection.
ast-grep outline lib/home/getValuationHeroState.ts
printf '\n--- getValuationHeroState.ts ---\n'
cat -n lib/home/getValuationHeroState.ts | sed -n '1,220p'

printf '\n--- getValuationHeroState.test.ts ---\n'
cat -n lib/home/__tests__/getValuationHeroState.test.ts | sed -n '1,260p'

printf '\n--- catalog selection references ---\n'
rg -n "catalogs\\[0\\]|selected catalog|first catalog|exactly one catalog|multiple catalogs|catalog.*order" lib hooks components types --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: recoupable/chat

Length of output: 11202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any selected-catalog state or artist-to-catalog ownership mapping.
rg -n "selectedCatalog|currentCatalog|catalog.*selected|setSelectedCatalog|catalog_id.*artist|artist.*catalog_id|catalogs\\[0\\]" hooks components providers lib types --glob '!node_modules' --glob '!dist' --glob '!build'

Repository: recoupable/chat

Length of output: 894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the catalog schema and any relations to accounts/artists.
rg -n -A12 -B4 "catalogs:|catalog_id|account_id.*catalog|catalog.*account|foreignKeyName.*catalog" types/database.types.ts lib hooks components --glob '!node_modules' --glob '!dist' --glob '!build'

printf '\n--- catalog table slices ---\n'
sed -n '1,220p' types/Catalog.ts
printf '\n--- getCatalogs.ts ---\n'
sed -n '1,220p' lib/catalog/getCatalogs.ts

Repository: recoupable/chat

Length of output: 20397


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the catalog table definition and its foreign keys in the generated database types.
rg -n -A20 -B6 "catalogs: \\{" types/database.types.ts
rg -n -A12 -B4 "catalogs_.*fkey|catalog_id.*fkey|account_id.*fkey" types/database.types.ts

Repository: recoupable/chat

Length of output: 10995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the catalog table definition and its foreign keys in the generated database types.
rg -n -A20 -B6 "catalogs: \\{" types/database.types.ts
rg -n -A12 -B4 "catalogs_.*fkey|catalog_id.*fkey|account_id.*fkey" types/database.types.ts

printf '\n--- getCatalogs.ts ---\n'
sed -n '1,220p' lib/catalog/getCatalogs.ts

Repository: recoupable/chat

Length of output: 12323


Resolve the catalog before calling measurements
account_catalogs is many-to-many, so catalogs[0] is arbitrary here. For accounts with more than one catalog, this can show the wrong catalog name/value or hide a valid artist-scoped measurement. Pick the intended catalog explicitly instead of relying on array order.

🤖 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 `@hooks/useHomeValuation.ts` around lines 33 - 37, Resolve the catalog
selection in useHomeValuation before calling useCatalogMeasurements, since
catalogs[0] is arbitrary for the many-to-many account_catalogs relationship.
Update the catalog lookup logic to choose the intended catalog explicitly based
on the current account or artist context instead of array order, and keep the
selected catalog value flowing into useCatalogMeasurements so the correct
catalog name/value and artist-scoped measurement are used.

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

1 issue found across 17 files (changes from recent commits).

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/catalog/getCatalogMeasurements.ts">

<violation number="1" location="lib/catalog/getCatalogMeasurements.ts:29">
P2: Add a try/catch wrapper with console.error logging before re-throwing, matching the established error-logging pattern used by every other catalog API fetcher (getCatalogs, getCatalogSongs, getSongsByIsrc, postCatalogSongs). Without it, HTTP and network errors from this endpoint will be thrown without the diagnostic console.error that the rest of the codebase emits, making production debugging harder.</violation>
</file>

<file name="hooks/useHomeValuation.ts">

<violation number="1" location="hooks/useHomeValuation.ts:34">
P2: Artist-scoped valuation can disappear for multi-catalog accounts because this always queries the first catalog before applying `artist_account_id`. Consider selecting the catalog that contains the selected artist, or changing the data path to query the artist scope across all account catalogs instead of anchoring to `catalogs[0]`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread hooks/useHomeValuation.ts
…_song_count aggregate

Amendment per the 2026-07-07 decision on chat#1850 (docs#267 respec,
api#763 amendment, database#42 RPCs): the v2 measurements response now
carries whole-scope SQL aggregates and a caller-paginated rows array.

- getCatalogMeasurements/useCatalogMeasurements accept a limit param
  (react-query key includes it); the hero requests limit=1 since it only
  needs the aggregates, not the rows
- getValuationHeroState reads measured_song_count instead of
  measurements.length: zero -> hide (nothing measured in scope), absent
  -> hide (pre-v2 shape whose numbers come from a capped read)
- echo-field gating unchanged: an artist-selected hero renders only when
  the response echoes that exact artist_account_id

TDD: tests adjusted RED first; full suite 106 tests, tsc 8 pre-existing
errors (0 new), prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

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

0 issues found across 6 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

…gId}/measurements

REST amendment on chat#1850: path for identity, query for modifiers.
getCatalogMeasurements builds the dynamic-segment URL; the query string
carries only artist_account_id/limit. No behavior change in the hero.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Amended to the final v2 contract (86e91310) — whole-scope aggregates + path-based endpoint

Follows the two 2026-07-07 decisions on #1850, building on the 67afc145 rework:

1. Aggregate-driven hero (d9f076c4). The v2 response now carries whole-scope SQL aggregates and a caller-paginated rows array (docs: recoupable/docs#267, api: recoupable/api#763, DB RPCs: recoupable/database#42). The hero:

  • reads measured_song_count (a single SQL aggregate over the entire scope) instead of measurements.length — the count can never again be a page-size artifact like the "1000 tracks measured" cap bug
  • requests limit=1 since it needs no rows, only the aggregates
  • hides when measured_song_count is 0 (nothing measured in scope) or absent — an absent count means a pre-v2 deployment whose numbers come from the capped read

2. Path-based endpoint (86e91310). getCatalogMeasurements now calls GET /api/catalogs/{catalogId}/measurements (path for identity, query for the optional artist_account_id/limit modifiers — mirrors /api/research/albums/{id}/measurements).

Echo-field gating (unchanged, and why): with an artist selected the resolver requires response.artist_account_id === selectedArtist.account_id before rendering. Deployments that predate the filter ignore the unknown param and return whole-catalog numbers with no echo — without the gate the hero would label another scope's money with the artist's name, the exact trust bug this issue exists to kill. Until api#763 deploys, an artist-selected hero therefore hides (and with the new endpoint path, pre-deploy previews 404 → same graceful greeting fallback); the no-artist whole-catalog hero also stays hidden against the v1 endpoint (no measured_song_count), by design — its total was computed over at most 1,000 rows.

Verification: suite 106 tests on this branch / 123 at the top of the stack, tsc 8 pre-existing errors (0 new), prettier clean. Stack rebased: chat#1853 → eb7e0632, chat#1854 → 19d3b250. Re-run the authenticated artist-switching pass (Elvis $14.6M vs September Mourning $173K on the [TEST] fixture) once api#763 merges.

🤖 Generated with Claude Code

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

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

Comment thread components/VercelChat/chat.tsx Outdated
* hero above the chat area instead (recoupable/chat#1850). Defaults to
* false so every existing mount keeps the greeting.
*/
hideGreeting?: boolean;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

YAGNI - this hideGreeting is a code smell. Why is it required? Why are we supporting 2 distinct, UI flows instead of unifying to one, consistent flow? If catalog info is missing, we should add a CTA rather than a generic greeting. right? Why does Vercel Chat need knowledge of this? What would happen if we deleted any greeting from this chat component to follow SRP keeping the responsibility in a standalone greeting / catalog component?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You were right on all four counts — fixed in 72262809:

Why did it exist? It was scar tissue: PR 1 needed the greeting suppressed when the hero rendered, and the stack 'fixed' it two PRs later (1854 replaced it with a modules prop) — which was the same smell relocated. Neither should have put UI-flow knowledge in the chat component.

What happened when we deleted greeting knowledge from chat.tsx: it got simpler and more general. Chat now exposes one generic emptyStateHeader?: ReactNode slot and renders whatever it's given — zero greeting/hero imports, no branching. There's also now exactly ONE empty-state layout for every mount (flex spacers inside a scrollable container: short content centers, tall content scrolls with the input docked beneath) — the two distinct flows are gone at the layout level too.

Where the responsibility went: a standalone components/Home/HomeHeader.tsx owns the homepage decision — valuation hero when measured data exists for the scope, otherwise ChatGreeting plus a 'Get a free catalog valuation →' CTA into the funnel (your point about the dead-end greeting). The session chat route passes <ChatGreeting /> explicitly; / and /chat pass <HomeHeader />.

hideGreeting is deleted repo-wide (0 refs). Stack re-rebased with 1854's modules prop unified into the same slot (suggestedPrompts survives as its real addition): 1853 1aec69d4, 1854 94d4a48f — 123/123 at the top, tsc 0 new, prettier clean.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Superseding my previous reply — you then asked the sharper question (why touch chat.tsx at all / emptyStateHeader is prop drilling), and you were right on both: the slot was still the same smell, relocated. Reworked in 540a53c4:

chat.tsx, NewChatBootstrap and HomePage are now byte-identical to main in this PR (verified with git diff origin/main — empty). The extension point already existed: chat.tsx has always delegated its empty-state header to ChatGreeting. That component now IS the standalone greeting/catalog component from your first comment — it renders the artist-scoped valuation hero when measured data exists for the current scope, otherwise the greeting plus the 'Get a free catalog valuation →' CTA. All context arrives through provider-backed hooks (useHomeValuation → ArtistProvider + react-query), zero new props — context management, not prop threads.

Bonus from doing it this way: every empty-chat surface (/, /chat, /sessions/{id}/chats/{id}) gets the same single flow for free, because they all already render ChatGreeting.

Stack restacked from the pre-slot commits: 1853 e2af7d41 (tasks module, HomePage-only), 1854 962e621b (its modules/chips design restored — chat.tsx changes now live only in that PR, where they can be debated on their own merits). 123/123 at the top, tsc 0 new, prettier clean.

sweetmantech added a commit that referenced this pull request Jul 7, 2026
…ader slot + unified HomeHeader (SRP/YAGNI)

Review feedback on #1852 (chat.tsx hideGreeting): the chat component was
carrying greeting knowledge and two UI flows. Chat now exposes a generic
emptyStateHeader slot and renders whatever it's given; hideGreeting is
deleted. Each mount composes its own header: the homepage and /chat pass
HomeHeader — ONE flow that shows the artist-scoped valuation hero when
measured data exists, otherwise the greeting plus a 'Get a free catalog
valuation' CTA into the funnel (no more bare greeting dead-end) — and the
session chat route passes ChatGreeting explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

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

0 issues found across 8 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

…tsx changes, no prop drilling (KISS/OCP)

Review feedback on #1852: chat.tsx should not be modified in this PR, and
header composition via props is drilling context that providers already
own. ChatGreeting — the component chat.tsx already delegates its
empty-state header to — becomes the artist header: the artist-scoped
valuation hero when measured data exists for the current scope (via
useHomeValuation, provider-backed), otherwise the greeting plus a 'Get a
free catalog valuation' CTA into the funnel. chat.tsx, NewChatBootstrap
and HomePage are byte-identical to main in this PR; every empty-chat
surface gets the same single flow for free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sweetmantech sweetmantech force-pushed the feat/home-valuation-hero branch from 7226280 to 540a53c Compare July 7, 2026 14:07
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Final live verification — 540a53c4 (ChatGreeting-internal, zero chat.tsx changes) — the artist-switching pass ✅

Signed into the branch-alias preview as the multi-artist test account. The value follows the artist — the trust bug this PR exists to kill is verifiably dead:

Selected artist Hero showed SQL ground truth (mid / measured) Match
Ana Bárbara $8.1M · $5.6M–$11.4M · 154 tracks $8.1M / 154
Apache (switched) $6M · $4.1M–$8.5M · 208 tracks $6,030,019 / 208

Ana Bárbara — her scoped valuation
Apache — the value changed with the artist

Network proof: GET /api/catalogs/7d3e5c35…/measurements?artist_account_id=ebae4bb9…&limit=1 — the v2 RESTful path, artist filter applied server-side, limit=1 (the hero reads measured_song_count, fetches no rows), echo gate satisfied (hero rendered ⇒ response echoed the requested scope). The pre-switch hydration fetch without the artist param correctly did not render under an artist label (echo null ≠ selected artist).

Unified surface check: /chat renders the same header ($6M/208 for Apache) — one flow everywhere ChatGreeting mounts, as designed.

Not exercised live: the greeting + "Get a free catalog valuation →" CTA branch — every artist in this account's roster now has measured songs (the fixture is thorough to a fault), so no zero-data artist was available; the branch is unit-covered and renders for any account/artist without measurements.

Merge order reminder: docs#267 ✅ · database#42 ✅ · api#763 ✅ (all live) → this PR is ready to merge → re-target chat#1853 (e2af7d41) → chat#1854 (962e621b).

🤖 Generated with Claude Code

@sweetmantech sweetmantech merged commit 58e2071 into main Jul 7, 2026
3 checks passed
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