Skip to content

New-customer funnel delivers the Catalog HQ promise: land the canonical-artist P1 quartet + in-flight fleet, close the remaining funnel gaps #1859

Description

@sweetmantech

✅ CLOSED 2026-07-08 — P1 shipped, verified, and fully closed. The new-customer funnel works end-to-end: a fresh signup completes a valuation and sees their number on the chat homepage (live witness), zero duplicate artists minted, all pre-existing damage remediated (health-check → 0). The remaining work — extending valuation coverage to existing customers (588/608 rostered accounts, analysis) plus the still-open homepage-honesty fleet — moves to the successor chat#1863, which carries all 10 open PRs. Connector-security hardening tracks separately in chat#1860.

Tracking issue for making the live funnel deliver what the Catalog HQ launch promised to brand-new customers — successor to chat#1850 (closed; its homepage stack — valuation hero, tasks module, prompt chips — is fully live on prod as of 2026-07-07, and the launch video is published on all four platforms). The 2026-07-07 fresh-account walkthrough proved the gap this issue closes, and the 2026-07-08 walkthrough re-confirmed it live at the network layer: a new signup completes a $2.1M valuation and lands on a homepage that shows a greeting instead of their number. Every open item and in-flight PR from #1850 moves here. (Business context, once: launch-video traffic is arriving now; the funnel's first impression is the retention wedge.)

Goal

A brand-new customer who values a catalog on recoupable.dev opens chat.recoupable.dev and sees their number, with zero extra steps and honest labeling throughout:

  1. The valuation resumes automatically after the auth code (no second "Value my catalog" click) — marketing/hooks/useCatalogValuation.ts.
  2. The funnel attaches the canonical artist to their roster (no duplicate artist mint), so the artist-scoped hero (GET /api/catalogs/{catalogId}/measurements?artist_account_id=…) returns their value — marketing/lib/valuation/linkArtistToAccount.ts retired in favor of api-side claim attach.
  3. The homepage modules read honestly: task runs carry real titles, the starter card offers the valuation report, and an in-progress valuation is labeled partial on the marketing card.
  4. The two computeCatalogValuation/computeValuationBand implementations cannot silently drift (golden-fixture guard).

Done at the macro level when: the fresh-account walkthrough (recoupable.dev search → value → OTP → auto-run → CTA → chat homepage) shows the same dollar figure end-to-end with no dead ends — witnessed live on prod with a new plus-alias account. ✅ SATISFIED 2026-07-08 (live witness): sweetmantech+july820261150@… went search → $2.1M card → claim → chat hero $2.1M / 67 tracks; hero call scoped to canonical 7647f901 returned 67 / 557,734,241 / $2.06M (was 0/$0 that morning); zero duplicate artist minted. Remaining to fully close: heal pre-existing damaged accounts (backfill pass-2 + cleanup-delete).

PRs (updated 2026-07-08 — ship-priority order, top to bottom; 11 open, 6 merged)

PR Item Base State
docs#269 P1: claim contract documents the roster-attach side effect main ✅ merged 2026-07-08 — see Done
api#768 P1: claim-time canonical attach (ISRCs → song_artists dominant → roster) + socials-at-ingest (case-preserved) main ✅ merged 2026-07-08 — see Done; prod deploy is the gate for marketing#46
database#43 P1: backfill — repoint funnel-duplicate roster artists to canonical twins + copy Spotify socials main ✅ merged 2026-07-08 — see Done. Post-merge verification found stragglers need two remedies, both gated on marketing#46 (see the two backfill-follow-up rows below)
marketing#46 P1: delete linkArtistToAccount — the duplicate mint (claim attach becomes the roster source of truth) main ✅ merged 2026-07-08 — see Done. Forward minting stopped → the backfill follow-ups below are now unblocked
database#44 P1 backfill pass 2 (repoint): re-run the idempotent repoint for accounts that don't yet hold the canonical (Category A) main ✅ merged + applied 2026-07-08 — verified live: 2 Category A rows flipped to canonical (Black Dave fb678396→aa87964a, Del Water Gap 848dd28d→7647f901); health-check Category A 2 → 0, Category B still 5 (→ #45)
database#45 P1 backfill cleanup-delete: remove 5 redundant Category B dupe rows + delete orphaned funnel-dupe artist accounts (23 total) main ✅ merged + applied 2026-07-08 — verified live: 5 redundant rows + 23 orphaned dupe accounts deleted; health-check → 0; canonicals intact (closeout)
tbd — design note → api + chat ⭐ HIGH: auto-fill catalogs for all rostered accounts — materialize a catalog from each account's roster→songs graph so the homepage hero shows a valuation for every account, not only funnel-claimed ones main ⬜ not started — the single biggest hero-coverage lever: 588 of 608 rostered accounts (~97%) see no valuation today, all because no catalog was ever created (analysis). Needs a decision note first (trigger, cost control at 588-account scale, refresh cadence, ownership)
marketing#42 Valuation card: collapse the $0/0-stream release tail main 🔄 open — grouping logic TDD'd; pnpm build green; re-confirmed live 2026-07-08: 22 $0 · 0 streams rows directly above the CTA at the moment of maximum trust
marketing#45 Intent resume: pending intent persisted to sessionStorage at the auth gate, resumed on auth flip AND mount; 15-min staleness guard main 🔄 open — TDD (80/80), build green. Note: the 07-08 walkthrough auto-resumed successfully — the ref death is remount-dependent/intermittent, not universal; still ship as hardening
chat#1856 Starter card: prefer the "Weekly valuation + streams report" /agents template (live in prod, id ad0a233b…) main 🔄 open — 127 tests; gap re-confirmed live 2026-07-08 (fresh account offered the generic dashboard); live empty-state pass on prod fresh account after merge
docs#268 Run titles: TaskRunResponse gains nullable title (contract) main 🔄 open — merge first of the trio
api#764 Run titles: reverse filter[schedule] lookup against scheduled_actions (Trigger list rows carry no schedule ref) main 🔄 open — 3,950 tests; fails open both layers; authed pass pending (verification)
chat#1857 Run titles: HomeRunRow renders the real title, getTaskDisplayName fallback main 🔄 open — 131 tests; zero regression either side of the api#764 deploy
api#765 + marketing#44 Drift guard: byte-identical golden valuation fixture + lockstep tests in both repos (design note; shared package deferred) main 🔄 both open — both GREEN first run; zero divergence re-confirmed live 2026-07-08 (card $2.1M ↔ api mid $2,058,990); land as a same-day pair
marketing#43 Marketing header auth state: signed-in indicator + Open app + logout main 🔄 open — pnpm build green; live signed-in pass pending. Elevated context: the missing logout is half of the stale-session dead end (see Open)
mono#45 Root AGENTS.md: retire stale test-branch PR guidance; rides along: internal issue-workflow skills section main 🔄 open — docs-only; ⚠ now CONFLICTING with main, needs a rebase
tbd — marketing Label in-progress valuations as partial on the card (completed runs reconcile — verified twice at $2.1M/67; only partial-as-final display remains) main ⬜ not started

Merge sequencing: the table is in ship-priority order (churn/trust in the marketing→chat handoff first). P1 core docs#269 ✅ → api#768 ✅ (deployed) → database#43 ✅ → marketing#46 (next) → backfill pass 2 (repoint) → backfill cleanup-delete. marketing#46 deletes linkArtistToAccount, today's only forward creator of funnel roster artists; it was gated on api#768's claim attach being live (now satisfied). The two backfill follow-ups come AFTER marketing#46, not before — running the repoint while linkArtistToAccount is still minting just re-accumulates duplicates, and the cleanup-delete has the same problem; both converge only once #46 stops the forward mint (then repoint, then cleanup-delete → health-check hits 0). marketing#42/#45 ride the same marketing deploy train as #46. Titles trio docs#268 → api#764 → chat#1857 (contract-first; chat safe either side) — not observable on a day-one account, bites on the return visit. Fixture pair same-day. chat#1856, marketing#43, mono#45 independent.

Done

  • docs#269 — claim contract: POST /api/catalogs documents the roster-attach side effect.
    ✅ Shipped 2026-07-08 13:11 UTC (merged to main, f441eca).
    The OpenAPI contract for the claim endpoint now specifies that claiming (and re-claiming) a snapshot attaches the songs-graph canonical artist to the caller's roster — the contract api#768 implements. First merge of the P1 quartet, per docs-first sequencing.
    Verified: preview-checked before merge; the live api#768 preview matched this contract field-for-field and code-for-code (results matrix).

  • api#768 — claim-time canonical-artist roster attach + Spotify socials at ingest.
    ✅ Shipped 2026-07-08 13:45 UTC (merged to main, 1b8012b); prod deploy pending — marketing#46 must wait for it.
    POST /api/catalogs now resolves the claimed ISRCs' dominant song_artists account and inserts it into the claimer's account_artist_ids (first claims AND idempotent re-claims, so pre-fix accounts heal on the next CTA click; best-effort, never fails the claim). Capture enrichment now attaches case-preserved Spotify artist socials to auto-created canonical accounts. 3,966 tests green.
    Verified on the preview against the shared prod DB (documented-vs-actual matrix, 9/9 pass): first claim attached canonical 7647f901… (roster 17→18, zero artists minted), re-claim idempotent, healing re-attach confirmed, artist-scoped measurements returned 67 / 557,734,241 / $2.06M mid (vs 0/$0 on prod that morning), and 400/401/403/404 all matched the documented envelopes. Remaining before the item closes: prod deploy → marketing#46 → live fresh-funnel witness.

  • database#43 — backfill: repoint funnel-duplicate roster artists to their canonical twin + copy Spotify socials.
    ✅ Shipped 2026-07-08 15:05 UTC (merged to main, 2fe5af4); migration 20260707190000 had already been applied to prod (PR is the repo record; schema_migrations carries the version, no drift).
    One idempotent migration: repoints account_artist_ids.artist_id from funnel-minted song-less duplicates to their unambiguous song-owning canonical twin (5-condition conservative match), and copies the duplicate's Spotify social onto the canonical when it has none. Duplicates are orphaned, not deleted; ends with a post-condition that raises if any selected row wasn't repointed.
    Verified live: 14 roster rows repointed, 11 socials copied on the applied run; repro flipped from 0 songs / $0 to 67 / 557,513,905 on the 07-07 walkthrough catalog; idempotency re-run-verified; schema_migrations records 20260707190000 once (no drift, CI deploy skipped re-apply). Still owed (two remedies — see post-merge verification): a repoint re-run after marketing#46 (2 accounts today, Category A) and a separate cleanup-delete the re-run can't do (5 accounts today, Category B — dupe rows the migration skips because the canonical is already linked). Tracked as the two new matrix rows.

  • marketing#46 — stop minting a duplicate roster artist per funnel signup.
    ✅ Shipped 2026-07-08 16:46 UTC (merged to main, d006b91). Closes the P1 quartet.
    Deletes lib/valuation/linkArtistToAccount.ts + its test and its call in useCatalogValuation.doRun — the funnel no longer client-side-mints a song-less artist (POST /api/artists + PATCH …/{id}) per signup. Roster attach is now solely api-side at claim time (api#768). 62 tests green.
    Verified on the branch preview (results matrix): full funnel run captured 91 requests with zero POST/PATCH to /api/artists; the run still completes (POST /research/measurement-jobs 202 + 36 measurement polls, card $2.1M/558M/67/36) and the retained captureRunLead (POST /api/valuation/lead 200) is the only post-run write. Claim→attach half not exercised on the preview (marketing preview → test-api; verified separately on api#768 against prod DB). Forward minting is now stopped — the backfill pass-2 + cleanup-delete are unblocked.

Open — the fresh-funnel promise (in ship order; the P1 first)

  • P1: funnel accounts' roster artist is a socials-only duplicate — the artist-scoped hero hides.FULLY CLOSED 2026-07-08. Quartet merged + deployed (docs#269 · api#768 · database#43 · marketing#46); backfill closed (database#44 repoint + database#45 cleanup). New-customer path witnessed live (fresh signup → chat hero $2.1M / 67 tracks, zero duplicate minted); all pre-existing damage remediated, health-check query returns 0.

    • Why: 2026-07-07 walkthrough (sweetmantech+july720261741@gmail.com, catalog 4b934253…): the 67 songs were all linked to the canonical Del Water Gap (7647f901…) — 70 song_artists rows, zero for the account's roster artist, because linkArtistToAccount mints a duplicate artist per signup (POST /api/artists {name}, no dedup — audit, independent verification). Re-proven live 2026-07-08 at the network layer: fresh signup minted duplicate cfc0503f…; unscoped measurements 67 / $2.06M mid, artist-scoped 0 / $0 (walkthrough). Canonical-lookup caveat: the canonical artist has no account_socials row, so resolution goes through the songs graph (ISRCs → dominant song_artists artist), as api#768 implements.
    • Follow-through (two remedies — discovered in post-merge verification, 2026-07-08): the health-check query returns 7 stragglers today, all explained, none a backfill failure — but they split into two categories that need different fixes:
      • Category A (repoint, 2 accounts): account does not yet hold the canonical.FIXEDdatabase#44 merged + applied 2026-07-08; both rows flipped to canonical, health-check Category A count 2 → 0.
      • Category B (cleanup-delete, 5 rows + 16+ orphaned accounts): account already holds the canonical, so the hero already works, but a redundant dupe roster row lingers that the repoint deliberately skips — plus the orphaned dupe artist accounts Tech322/get artist #43 left behind. database#45 (open) deletes both. Merge after Logo swap #44.
    • Done when: a brand-new funnel account's homepage hero shows the value from their completed valuation card, witnessed live on prod ✅ (passed 2026-07-08live witness: fresh account, hero $2.1M / 67 tracks, canonical-scoped call 67 / 557.7M / $2.06M, zero duplicate minted); no duplicate artist rows minted on signup ✅; and the health-check query (verification comment) returns 0 rows after the repoint re-run + the cleanup-delete ⬜ (the only remaining piece — heals pre-existing damaged accounts).
  • Marketing card honesty: label partial valuations; collapse the $0 tail. (→ marketing#42; partial labeling _tbd_)

    • Why: the card can present a partial measurement as final (07-06: $1.4M partial vs $2.1M complete on the same artist); completed runs reconcile (verified twice), so only the display bug remains. Separately, the $0 · 0 streams tail sat directly above the CTA in both walkthroughs (23 rows 07-07, 22 rows 07-08).
    • Done when: an in-progress valuation is visibly labeled partial; no $0 rows render; a completed card matches the materialized catalog (already holding: 67 = 67).
  • Intent resume: "Value my catalog" auto-runs after the OTP — every time. (→ marketing#45)

    • Why: the pendingRun ref mechanism dies on the remount fresh-signup auth can cause (audit); the 07-07 walkthrough dead-ended on an idle homepage until a second click. The 07-08 walkthrough auto-resumed fine with sessionStorage confirmed empty — so the failure is remount-dependent/intermittent, and marketing#45 is hardening against the bad path, not the sole enabler.
    • Done when: search → Value my catalog → OTP → valuation runs with no further clicks, on a fresh account.
  • Homepage module honesty: real run titles; valuation starter template. (→ docs#268 · api#764 · chat#1857 · chat#1856)

    • Why: run rows all read "Scheduled Task" (verified live 2026-07-07); the starter card offered a generic dashboard instead of the valuation report the video shows (re-confirmed on a fresh account 2026-07-08). Prioritization note: titles are invisible to a day-one account (no runs yet) — chat#1856 outranks the titles trio for the new-customer funnel. Perf note for the titles api: one Trigger.dev call per schedule per list load — cache/TTL if schedule counts grow.
    • Done when: signed-in homepage lists runs by task title (fallback intact); fresh account's starter card offers "Weekly valuation + streams report"; verified live post-deploy.
  • Drift guard: valuation formula lockstep. (→ api#765 · marketing#44)

    • Why: the formula is implemented twice (api mirrors marketing); the launch copy claims the numbers cannot drift. Zero divergence re-confirmed live 2026-07-08 (card $2.1M ↔ api mid $2,058,990). Hardening nit for the PR or a follow-up: the byte-identical fixture invariant is discipline-enforced — assert the fixture SHA in each test so a lone-repo edit fails loudly. Shared package remains the endgame.
    • Done when: both suites green against the identical fixture; a deliberate single-repo formula change fails that repo's CI.
  • NEW (found 2026-07-08): stale-Privy-session dead end — "User already has one email account linked." (no PR yet; marketing#43's logout is necessary but not sufficient)

    • Why: on a device with a leftover Privy session (the httpOnly session cookie at privy.recoupable.dev survives localStorage/cookie clears on the top-level origins, and marketing has no logout), entering a new email + a valid OTP fails with the cryptic Privy link-conflict error and no recovery path — reproduced live 2026-07-08. Real-world shape: shared laptop, or a device where someone else tried the funnel first. The auth modal treats the flow as linking an email to the stale user instead of signing in fresh.
    • Fix: handle the link-conflict error in the marketing auth flow — on this error, log out the stale Privy session and retry as a fresh login (or surface an explicit "sign out and try again" action); land marketing#43's logout as the manual escape hatch.
    • Done when: on a device holding a stale session for account A, entering account B's email + valid OTP signs into B (or a one-click recovery is offered), verified live.
  • NEW (found 2026-07-08): circular homepage CTA — catalog-owning accounts are offered "Get a free catalog valuation →" back to marketing. (no PR yet; adjacent to the P1 hero)

    • Why: the signed-in homepage's fallback state (artist-scoped hero empty) shows a link to recoupable.dev/valuationwitnessed 2026-07-08 by an account that completed a $2.1M valuation 60 seconds earlier. Once the P1 lands the hero should replace it for funnel accounts, but the fallback must never offer the marketing funnel to an account that already owns a catalog (honest alternative: an in-progress/"we're linking your catalog" state, or a link to their catalog page).
    • Done when: an account owning ≥1 catalog never sees the marketing valuation CTA on the homepage — hero, in-progress label, or catalog link instead; verified on the fresh-funnel account.

Open — carried funnel/platform items (from #1850, unblocked, pick from the top)

  • Verify-and-close: the 1,000-row aggregate cap. Believed fixed by api#763's whole-scope SQL aggregates (merged 2026-07-07). One authed check: GET /api/catalogs/7d3e5c35…/measurements?limit=1 reports measured_song_count 2,679 and a band consistent with 16.31B streams (original repro) → then close.
  • api: the artistName filter on GET /api/catalogs/songs no-ops on unlinked songs. Latent since api#681 (LEFT-join); returns every row for a non-member artist. No longer load-bearing for the hero (v2 filter shipped) — still a correctness bug for other consumers. RED test first: filtered query on a catalog with unlinked songs returns 0.
  • Data bug: lowercased Spotify artist ids in socials.profile_url (10 of 15 roster artists) — album resolution 502s. api#768's ingest is case-preserved (forward path); remaining: find the normalize-on-write culprit + backfill existing rows (lower(id) match makes it mechanical).
  • Ops: chat previews' Privy test app is at its user cap. Blocks fresh-account testing on previews ("test application has reached its user limit"). Workaround in use: fresh-account passes run on prod with plus-aliases. Fix: upgrade/raise the cap in the Privy dashboard; decide the preview api-base story (previews call test-recoup-api.vercel.app, no longer auto-synced).
  • Ops nit: flip the recoupable.com redirect from 307 to 301/308 (Vercel domain settings) so six years of .com link equity transfers. Redirect itself restored + verified 2026-07-07.

Open — design (needs a decision note before implementation)

  • ⭐ HIGH — Auto-fill the account catalog for pre-funnel accounts (roster → songs graph), so the hero lights up for every account, not just funnel-claimed ones. (raised by sweetman 2026-07-06; prototype worked — 15 artists / 880 releases via measurement-jobs + catalogs/songs)
    • Scale (quantified 2026-07-08, analysis): 588 of 608 rostered accounts (~97%) see no valuation on any artist — every one of them because a catalog was never materialized (there is no "has catalog but hero dark" cohort; all 20 catalog-owners resolve correctly post-P1). This is the biggest remaining hero-coverage lever: closing it moves coverage from ~3% to ~100%.
    • Distinct from the P1 (that was funnel accounts whose catalog pointed at a duplicate artist; this is the ~97% who never had a catalog at all).
    • Open questions for the decision note: trigger (on artist-add? on first chat? a backfill sweep?), cost control at 588-account scale (measurement-job spend across every roster), catalog shape, refresh cadence, ownership semantics.
    • Done when: a decision note answers those and links an implementation tracker; ultimately, the coverage query returns ~0 accounts-with-roster-but-no-valuation.

Architecture decisions (carried from #1850)

  • Homepage = valuation-first dashboard ("Catalog HQ"); chat = command bar. (sweetman, 2026-07-06)
  • Valuation band derived at read time, server-side; one formula. Two implementations temporarily guarded by the byte-identical golden fixture; a shared package is the endgame.
  • Artist scoping happens inside the measurements endpoint (artist_account_id filter via catalog_songs ∩ song_artists); per-artist catalogs rejected. REST shape: path for identity, query for modifiers.
  • Claim-time canonical attach is the roster source of truth for funnel signups (2026-07-08, from the P1 audit): find/reuse the canonical artist via the songs graph — never mint per-signup duplicates. linkArtistToAccount retires with marketing#46.
  • All homepage data flows through api.recoupable.dev endpoints — no direct Supabase reads from chat.

Source references

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions