feat(onboarding): seed the catalog by kicking /api/valuation on first artist add (chat#1867) - #1879
Conversation
… artist add Closes the onboarding catalog dead-end for direct signups. When the first artist is added via the Spotify search (#1878 already links their Spotify profile), useAddSpotifyArtist now fire-and-forget kicks POST /api/valuation { spotify_artist_id } (api#776) — only when the account has no catalog yet — which materializes the catalog + value band in ~20s. So the "Claim your catalog" step is already complete by the time the user reaches it, and they flow through to the first-task step (weekly report emails). - lib/valuation/runValuation.ts — client POST /api/valuation (TDD 2/2). - hooks/useAddSpotifyArtist.ts — background kick on first add (gated on an empty, loaded catalogs query), surfaced via a toast.promise ("Valuing … → Your catalog is ready"), invalidating the catalogs query on success so the sequence advances. chat#1867 (seed onboarding catalog on first artist add). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe Spotify artist onboarding flow now detects an empty catalog, starts background valuation for the newly added artist, displays toast status messages, and invalidates catalog data after completion. A reusable authenticated valuation API helper was added. ChangesSpotify catalog seeding
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant useAddSpotifyArtist
participant ValuationAPI
participant QueryClient
User->>useAddSpotifyArtist: Add Spotify artist
useAddSpotifyArtist->>ValuationAPI: Add artist, then POST valuation when catalogs are empty
ValuationAPI-->>useAddSpotifyArtist: Valuation result
useAddSpotifyArtist->>QueryClient: Invalidate ["catalogs"]
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
3 issues found across 3 files
Confidence score: 3/5
- In
hooks/useAddSpotifyArtist.ts, onboarding can skip catalog seeding if an artist is selected beforeuseCatalogsfinishes loading, and the flow may never retry once catalogs arrive; this can leave new users with incomplete setup after merge — gate artist adds on a resolved catalog state or rerun seeding when catalog loading completes. - In
hooks/useAddSpotifyArtist.ts, the success toast can appear beforequeryClient.invalidateQueries()actually finishes because its promise is not returned from the.then()chain; users may see “ready” while data is still stale — return/await the invalidation promise so completion messaging reflects real readiness. - In
hooks/useAddSpotifyArtist.ts, rapid back-to-back artist adds can launch overlapping valuation runs afterisAddingis reset too early, risking duplicate or conflicting background work; this could create inconsistent post-add state — keep the add lock active until valuation scheduling is safely serialized or deduplicated.
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="hooks/useAddSpotifyArtist.ts">
<violation number="1" location="hooks/useAddSpotifyArtist.ts:51">
P2: Two rapid sequential artist adds can trigger concurrent valuations before the first one completes. Since `isAdding` resets to false right after the artist-add API returns (valuation runs fire-and-forget in the background), the UI allows adding another artist while the first valuation is still in flight. At that point the catalogs query hasn't been invalidated yet, so it still reports an empty catalog — causing a second `POST /api/valuation` to fire. Consider tracking a separate `isValuating` ref or skipping the valuation if one is already pending to avoid duplicate catalog creation.</violation>
<violation number="2" location="hooks/useAddSpotifyArtist.ts:52">
P1: Fresh onboarding can still miss catalog seeding when the user selects an artist before `useCatalogs` finishes loading. The `undefined` loading state fails this guard and nothing reruns after the catalog query becomes empty; defer/retry the seed once the query resolves, or prevent selection until this check is known.</violation>
<violation number="3" location="hooks/useAddSpotifyArtist.ts:53">
P2: The "Your catalog is ready" success toast fires before the catalog data refresh finishes. The `.then()` callback calls `queryClient.invalidateQueries()` but doesn't return its promise, so the outer promise resolves as soon as the invalidation is *scheduled* — not when it actually completes. This means there's a brief window where the toast claims the catalog is ready but the React Query cache hasn't been refreshed yet. If the user navigates to the catalog page right after the toast, they could see stale/empty data momentarily. Return the invalidation promise from the `.then()` callback so the toast waits for the full invalidation roundtrip.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // and fire-and-forget so the dialog closes immediately — the catalog | ||
| // materializes in ~20s and the sequence advances on the next landing. | ||
| const catalogs = catalogsQuery.data?.catalogs; | ||
| if (catalogs && catalogs.length === 0) { |
There was a problem hiding this comment.
P1: Fresh onboarding can still miss catalog seeding when the user selects an artist before useCatalogs finishes loading. The undefined loading state fails this guard and nothing reruns after the catalog query becomes empty; defer/retry the seed once the query resolves, or prevent selection until this check is known.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useAddSpotifyArtist.ts, line 52:
<comment>Fresh onboarding can still miss catalog seeding when the user selects an artist before `useCatalogs` finishes loading. The `undefined` loading state fails this guard and nothing reruns after the catalog query becomes empty; defer/retry the seed once the query resolves, or prevent selection until this check is known.</comment>
<file context>
@@ -33,6 +43,25 @@ export function useAddSpotifyArtist() {
+ // and fire-and-forget so the dialog closes immediately — the catalog
+ // materializes in ~20s and the sequence advances on the next landing.
+ const catalogs = catalogsQuery.data?.catalogs;
+ if (catalogs && catalogs.length === 0) {
+ toast.promise(
+ runValuation(accessToken, artist.id).then(() => {
</file context>
| // Only when we know there is no catalog yet (empty, not just unloaded), | ||
| // and fire-and-forget so the dialog closes immediately — the catalog | ||
| // materializes in ~20s and the sequence advances on the next landing. | ||
| const catalogs = catalogsQuery.data?.catalogs; |
There was a problem hiding this comment.
P2: Two rapid sequential artist adds can trigger concurrent valuations before the first one completes. Since isAdding resets to false right after the artist-add API returns (valuation runs fire-and-forget in the background), the UI allows adding another artist while the first valuation is still in flight. At that point the catalogs query hasn't been invalidated yet, so it still reports an empty catalog — causing a second POST /api/valuation to fire. Consider tracking a separate isValuating ref or skipping the valuation if one is already pending to avoid duplicate catalog creation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useAddSpotifyArtist.ts, line 51:
<comment>Two rapid sequential artist adds can trigger concurrent valuations before the first one completes. Since `isAdding` resets to false right after the artist-add API returns (valuation runs fire-and-forget in the background), the UI allows adding another artist while the first valuation is still in flight. At that point the catalogs query hasn't been invalidated yet, so it still reports an empty catalog — causing a second `POST /api/valuation` to fire. Consider tracking a separate `isValuating` ref or skipping the valuation if one is already pending to avoid duplicate catalog creation.</comment>
<file context>
@@ -33,6 +43,25 @@ export function useAddSpotifyArtist() {
+ // Only when we know there is no catalog yet (empty, not just unloaded),
+ // and fire-and-forget so the dialog closes immediately — the catalog
+ // materializes in ~20s and the sequence advances on the next landing.
+ const catalogs = catalogsQuery.data?.catalogs;
+ if (catalogs && catalogs.length === 0) {
+ toast.promise(
</file context>
| toast.promise( | ||
| runValuation(accessToken, artist.id).then(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["catalogs"] }); |
There was a problem hiding this comment.
P2: The "Your catalog is ready" success toast fires before the catalog data refresh finishes. The .then() callback calls queryClient.invalidateQueries() but doesn't return its promise, so the outer promise resolves as soon as the invalidation is scheduled — not when it actually completes. This means there's a brief window where the toast claims the catalog is ready but the React Query cache hasn't been refreshed yet. If the user navigates to the catalog page right after the toast, they could see stale/empty data momentarily. Return the invalidation promise from the .then() callback so the toast waits for the full invalidation roundtrip.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useAddSpotifyArtist.ts, line 53:
<comment>The "Your catalog is ready" success toast fires before the catalog data refresh finishes. The `.then()` callback calls `queryClient.invalidateQueries()` but doesn't return its promise, so the outer promise resolves as soon as the invalidation is *scheduled* — not when it actually completes. This means there's a brief window where the toast claims the catalog is ready but the React Query cache hasn't been refreshed yet. If the user navigates to the catalog page right after the toast, they could see stale/empty data momentarily. Return the invalidation promise from the `.then()` callback so the toast waits for the full invalidation roundtrip.</comment>
<file context>
@@ -33,6 +43,25 @@ export function useAddSpotifyArtist() {
+ // materializes in ~20s and the sequence advances on the next landing.
+ const catalogs = catalogsQuery.data?.catalogs;
+ if (catalogs && catalogs.length === 0) {
+ toast.promise(
+ runValuation(accessToken, artist.id).then(() => {
+ queryClient.invalidateQueries({ queryKey: ["catalogs"] });
</file context>
| toast.promise( | |
| runValuation(accessToken, artist.id).then(() => { | |
| queryClient.invalidateQueries({ queryKey: ["catalogs"] }); | |
| runValuation(accessToken, artist.id).then(() => { | |
| return queryClient.invalidateQueries({ queryKey: ["catalogs"] }); | |
| }), |
…5.5 (post-#1877) The featured-models refresh in #1877 replaced openai/gpt-5.2 with openai/gpt-5.5, but organizeModels.test.ts still asserted gpt-5.2 was featured — failing CI on test. Update the fixture + assertion to a currently-featured id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preview verification — the catalog dead-end is gone ✅Sha-checked preview Flow — zero manual catalog step:
DB after (account went from the dead-end to seeded):
A direct signup now flows add-artist → (auto socials + auto catalog) → Step 4 (first-task → weekly emails), with no manual catalog step. Also pushed |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/useAddSpotifyArtist.ts`:
- Around line 53-56: Update the promise chain in the toast.promise call within
the artist valuation flow so the then callback returns
queryClient.invalidateQueries({ queryKey: ["catalogs"] }). This keeps
toast.promise pending until catalog invalidation completes and propagates any
invalidation error.
In `@lib/valuation/runValuation.ts`:
- Around line 16-23: Update the valuation request in runValuation to use an
AbortSignal timeout or caller-provided signal, ensuring the fetch aborts and
rejects after a bounded duration. Preserve the existing request payload and
headers while allowing the surrounding toast.promise flow to recover from the
failure.
🪄 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: d2465049-c80f-47af-8016-b184c2e45337
⛔ Files ignored due to path filters (2)
lib/ai/__tests__/organizeModels.test.tsis excluded by!**/*.test.*and included bylib/**lib/valuation/__tests__/runValuation.test.tsis excluded by!**/*.test.*and included bylib/**
📒 Files selected for processing (2)
hooks/useAddSpotifyArtist.tslib/valuation/runValuation.ts
| toast.promise( | ||
| runValuation(accessToken, artist.id).then(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["catalogs"] }); | ||
| }), |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file and locate the referenced code.
git ls-files hooks/useAddSpotifyArtist.ts
wc -l hooks/useAddSpotifyArtist.ts
cat -n hooks/useAddSpotifyArtist.ts | sed -n '1,220p'
# Search for related patterns in the repository for context.
rg -n "toast\.promise|invalidateQueries\(\{ queryKey: \[\"catalogs\"\] \}\)" hooks . || trueRepository: recoupable/chat
Length of output: 4193
Return the invalidation promise from then. toast.promise currently resolves as soon as runValuation finishes, not after queryClient.invalidateQueries completes, so the success toast can fire before the catalog refresh and any invalidation error is dropped.
Proposed fix
- runValuation(accessToken, artist.id).then(() => {
- queryClient.invalidateQueries({ queryKey: ["catalogs"] });
- }),
+ runValuation(accessToken, artist.id).then(() =>
+ queryClient.invalidateQueries({ queryKey: ["catalogs"] }),
+ ),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| toast.promise( | |
| runValuation(accessToken, artist.id).then(() => { | |
| queryClient.invalidateQueries({ queryKey: ["catalogs"] }); | |
| }), | |
| toast.promise( | |
| runValuation(accessToken, artist.id).then(() => | |
| queryClient.invalidateQueries({ queryKey: ["catalogs"] }), | |
| ), |
🤖 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/useAddSpotifyArtist.ts` around lines 53 - 56, Update the promise chain
in the toast.promise call within the artist valuation flow so the then callback
returns queryClient.invalidateQueries({ queryKey: ["catalogs"] }). This keeps
toast.promise pending until catalog invalidation completes and propagates any
invalidation error.
| const res = await fetch(`${getClientApiBaseUrl()}/api/valuation`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| body: JSON.stringify({ spotify_artist_id: spotifyArtistId }), | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== file outline ==\n'
ast-grep outline lib/valuation/runValuation.ts --view expanded || true
printf '\n== file contents ==\n'
cat -n lib/valuation/runValuation.ts
printf '\n== references ==\n'
rg -n "runValuation\\(|valuation.*toast|toast.*valuation|AbortController|signal:" lib -SRepository: recoupable/chat
Length of output: 1797
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "runValuation\\(" .
rg -n "AbortController|signal:|timeout|setTimeout\\(" lib src app .Repository: recoupable/chat
Length of output: 4364
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline hooks/useAddSpotifyArtist.ts --view expanded || true
cat -n hooks/useAddSpotifyArtist.ts | sed -n '1,140p'Repository: recoupable/chat
Length of output: 3677
Bound the valuation request. lib/valuation/runValuation.ts:16-23 can hang indefinitely here, which leaves the toast.promise loading state stuck with no recovery path. Add an AbortSignal timeout or accept a caller-provided signal and fail fast on abort.
🤖 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/valuation/runValuation.ts` around lines 16 - 23, Update the valuation
request in runValuation to use an AbortSignal timeout or caller-provided signal,
ensuring the fetch aborts and rejects after a bounded duration. Preserve the
existing request payload and headers while allowing the surrounding
toast.promise flow to recover from the failure.
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.
Re-trigger cubic
…t-task emails, add-artist, Kimi K3 (#1880) * feat(onboarding): state-derived onboarding router with resume-on-landing + skip-for-now (#1872) * feat(onboarding): state-derived onboarding router with resume-on-landing + skip-for-now On every authenticated landing, chat home derives the onboarding step from the activation checkpoint predicates (has artists -> artists have socials -> has claimed catalog -> has enabled task) over existing data sources, never a stored wizard cursor (#1867). Incomplete accounts resume the sequence at the first unmet step; an always-visible skip drops to the app with a dismissible session-scoped checklist; activated accounts never see it. Step cards are titled placeholders linking to the existing pages until the step PRs land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(onboarding): review fixes — checklist clipped by right rail, account-scoped session flags, storage safety, fresh catalogs per landing, roster-error fail-open, h3 card heading Fixes the preview-verified dismiss bug and all cubic findings on #1872: - Dismiss live bug: the checklist was position:fixed to the viewport, so the z-[65] ArtistsSidebar rail overlapped its right edge and clipped the dismiss button off-screen. Now absolute within the (relative) home content area. Gate state was already single-owner (HomePage passes callbacks down) — locked in with a HomePage-level dismiss/skip/resume test. - P1 cross-account leak: skip/dismiss sessionStorage keys are scoped by account id and re-derived when the account in the tab changes (useOnboardingSessionFlags). - P2 storage safety: read/write go through safe helpers that no-op on throwing storage so the gate fails open instead of crashing. - P2 catalogs staleness: useRefreshCatalogsOnLanding invalidates the catalogs cache on onboarding mount for one fresh read per landing; useCatalogs behavior unchanged for other consumers. - P2 roster-error fail-open: useArtistsRoster now exposes isError; deriveOnboardingState keeps isReady false on roster error so the view resolves to none. - P3 hook length: projection extracted to pure deriveOnboardingState. - P3 heading hierarchy: step card h1 -> h3 under the container h2. Tests: TDD red-first; +26 tests (jsdom + @testing-library/react added as devDeps; vitest include extended to .test.tsx). 174 passing; tsc clean apart from the 7 pre-existing main errors in lib/emails tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(onboarding): make the skip checklist a persistent, non-dismissible reminder Remove the checklist-dismissed escape hatch entirely. After "skip for now" the bottom-right "Finish setting up" card is now always present while onboarding is incomplete (survives refresh via the session skip flag), and clicking it re-opens the sequence — there is no way to permanently hide it. Net-removal: drops the second session flag, its storage read/write usage, the dismissChecklist callback threading, the X/Dismiss button, and the separate "Resume setup" button (the card header is now the resume trigger). getOnboardingView collapses to two views (sequence | checklist) and OnboardingFlag to a single member. +47 / -134. Full suite 181 pass, tsc clean on touched files, prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(onboarding): restore plain title + add explicit "Continue" button The checklist header "Finish setting up" is a plain title again (it read as non-clickable). Re-opening the sequence is now a clearly-styled primary "Continue" button at the bottom of the card, replacing the ambiguous click-the-header affordance. Suite 181 pass, tsc clean on touched files, prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: post-valuation catalog report on /catalogs/[catalogId] (chat#1867 sequence step 1) (#1873) The marketing valuation CTA landed on a bare Catalog Songs admin screen. /catalogs/{id} now opens as a real catalog report: valuation echo (central value + range via the ported marketing formula), lifetime streams, tracks/releases measured, per-release table with album art, per-section diagnosis + prescription copy, and one primary CTA (set up your weekly report -> /tasks). The existing songs/ISRC management UI stays reachable as a secondary Manage songs tab. Valuation math mirrors marketing/lib/valuation (identical constants; age from the api's catalog_age_years, 5y default). Release rollups are null-safe on album/name/artist metadata independently of the sibling crash-fix PR. Reuses the chat#1852 useCatalogMeasurements hook, extending its response type with the v2 aggregate fields as optionals. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: roster + socials verification onboarding steps (chat#1867) (#1870) * feat: roster + socials verification onboarding steps (chat#1867) Two self-contained onboarding step components for the chat#1867 sequence, mounted standalone at /onboarding/roster so the slice is user-testable before the onboarding router lands: - ConfirmRosterStep: shows the auto-created artist(s) from the valuation flow, inline "add another artist" for multi-artist managers (existing POST /api/artists endpoint), confirm to advance. - VerifySocialsStep: per rostered artist, lists the auto-matched socials with handle + follower count; each match is confirmed or rejected, wrong matches are fixed by pasting the correct profile link (existing PATCH /api/artists/{id} profileUrls path), and artists with no socials record an explicit "none". Verification state is pure client-side logic (lib/onboarding/*, TDD'd): verdict reducers, per-artist and step-level resolution predicates, PATCH payload building with APPPLE->APPLE platform-key normalization, and a follower-count accessor tolerant of the API's snake_case rows behind chat's camelCase SOCIAL type. No new endpoints, tables, or context providers - hooks compose the existing ArtistProvider / OrganizationProvider / Privy auth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(onboarding): simplify verify-socials to accept-by-default (chat#1867) Per design review: drop the per-social Correct/Wrong verdicts, the resolution/gating machinery, and the explicit "This artist has no socials" button. Matches are accepted by default; the only actions are Edit (fix a wrong link, existing PATCH profileUrls path) and — for an artist with no matches — a soft nudge + "Add a profile". Continue always proceeds (empty = implicit none). Deletes the verdict code that would otherwise be merged and immediately removed: socialVerificationTypes, applySocialVerdict, isArtistSocialsResolved, areAllArtistsResolved, markArtistHasNoSocials, findSocialIdByPlatform, useSocialsVerification, SocialVerifyRow (+ their tests). Net −13 files. Note: "Remove/delete a social" (the other half of the design) needs a new api endpoint — the api's PATCH profileUrls is per-platform merge with no delete-social path — tracked as a fast-follow on chat#1867. Full suite 144 pass, tsc clean on touched files, lint + prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(onboarding): always offer Add-a-profile per artist, not just empty ones A matched-social list can still be missing platforms (e.g. Spotify found but no Instagram). Surface 'Add a profile' below every artist's socials, not only when zero were auto-matched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(onboarding): first-task step — pre-run weekly report + confirm schedule (#1871) Sequence final step for chat#1867 (respec 2026-07-20, pre-run shape): generate the first weekly catalog report immediately through the normal chat pipeline (same POST /api/chat transport a send uses), show it finished, then ask one question — "get this every Monday?". Confirm creates the enabled weekly task via the existing POST /api/tasks path (which also mints the schedule) and shows the next-run time; decline creates nothing. Mounted standalone at /onboarding/first-task so the step is user-testable before the OnboardingSequence container exists. The pre-run prompt and the scheduled task prompt come from one builder, so the preview the user confirms is byte-for-byte what Monday's run uses. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(onboarding): first-task weekly report actually emails — LA EQUIS template + Kimi K3 (chat#1867) (#1877) * feat(onboarding): first-task weekly report actually emails (LA EQUIS template + Kimi K3) The first-task prompt generated a report but never emailed it (email is a prompt-driven send_email call, and the prompt had no recipient or send instruction) — proven by firing the created task: the agent ran and persisted a report, but email_send_log stayed empty. Generalize buildFirstTaskPrompt from the proven LA EQUIS weekly report (scheduled_action 39fb5f68, running week over week): resolve the artist's Spotify id from its connected profile -> capture this week's play counts -> compute real week-over-week per-track deltas -> build a fully hex-specced inline-CSS HTML email -> send via the recoup-platform-email-helper skill -> confirm the Resend id. Carries the sandbox no-python and anti-fabrication guardrails verbatim (what makes the send reliable). Thread the account email (recipientEmail) + artistAccountId through both callers so the pre-run preview and the Monday scheduled task both send to the account holder. Bump both surfaces from DEFAULT_MODEL (openai/gpt-5.4-mini — completes the data calls but gives up before composing the long HTML email, the LA EQUIS lesson) to a new REPORT_MODEL (moonshotai/kimi-k3), which reliably composes and sends. chat#1867 (first-task email-prompt gap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(onboarding): default model -> Kimi K3 (KISS); harden email-less + shell paths Address review feedback on #1877: - Sweets (KISS): collapse the new REPORT_MODEL constant into DEFAULT_MODEL — set DEFAULT_MODEL = moonshotai/kimi-k3 app-wide (the fast gpt-5.4-mini default gave up before composing the long HTML report email). Revert both onboarding hooks to DEFAULT_MODEL; relabel the featured picker entry (id: DEFAULT_MODEL) from "GPT-5.4 Mini" to "Kimi K3" so it isn't mislabeled. - CodeRabbit (email-less login): a wallet/phone/social-only Privy account has no email — FirstTaskStep now shows a distinct "add an email" alert (not an infinite "Preparing…"), and useConfirmFirstTask's onError distinguishes EMAIL_REQUIRED from a generic retry. - Codex (shell-safety): artist/track/album names are user-entered; add a prompt guardrail to pass them as single quoted args so a name with quotes/;/$() can't alter the email-helper command. Codex "non-Pro users get rejected" P2 is a false positive: the prior default (gpt-5.4-mini) is also non-free by isFreeModel yet ships as the default, so the API does not hard-block non-free default models. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Update lib/consts.ts * chore(models): refresh featured model list to latest per series Sweets: while touching the featured list, bring every entry to its series' latest (verified present in the AI Gateway catalog): - GPT-5.2 -> GPT-5.5 - Claude Opus 4.5 -> 4.8 - Claude Sonnet 4.5 -> Sonnet 5 - Gemini 2.5 Flash Lite -> Gemini 3.5 Flash Lite - Gemini 3 Pro -> Gemini 3.1 Pro - Grok 4 -> Grok 4.5 (Kimi K3 already latest.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(chat): reuse the real Message renderer in the first-task pre-run (DRY) The onboarding pre-run rendered only the last assistant message's TEXT (getReportTextFromMessages -> <Response>), dropping tool-call and reasoning components — a downgraded, diverging copy of the chat UI. #1877's tool-heavy email workflow made the gap obvious (raw narration, no tool components). Reuse the normal chat's <Message>/<MessageParts> so tool calls/results get their real components. MessageParts was coupled to useVercelChatContext (status + reload); decouple it: both are now optional props with a context fallback, so the normal chat is behaviorally unchanged (props omitted -> context used) while the pre-run supplies them without mounting a provider (VercelChatProvider owns its own useChat instance, so wrapping the pre-run would double the chat). - VercelChatProvider: export VercelChatContext for the optional read. - MessageParts/Message: optional status/reload props, fall back to context. - useFirstTaskReport: expose messages + status. - FirstTaskReportRun: render assistant messages via <Message> (filtering out the auto-sent prompt user message), not a text dump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(artists): Add New Artist opens a shared Spotify search — kills the /artists loop (chat#1867) (#1878) * fix(artists): Add New Artist opens a shared Spotify search (kills the /artists loop) "Add New Artist" (and the sidebar/header equivalents) called toggleCreation, which pushed `/?q=create a new artist` and relied on the home CHAT to interpret it. The onboarding router (chat#1872) now intercepts the home for incomplete accounts and renders "Finish setting up" instead, so the query never ran — the "Open your roster" CTA bounced back to /artists → infinite loop. Replace the redirect with a shared Spotify artist-search dialog: - lib/spotify/parseSpotifyArtistResults.ts — pure parser for the GET /api/spotify/search?type=artist envelope (id/name/imageUrl/profileUrl/followers). - lib/artists/addSpotifyArtist.ts — create by name (POST /api/artists) then link the Spotify image + profile URL (PATCH), so the roster gets real data. - useSpotifyArtistSearch (debounced, abortable) + useAddSpotifyArtist hooks. - components/Artists/SpotifyArtistSearch.tsx — the shared typeahead (reused next for social enrichment + verify-socials). - components/Artists/AddArtistDialog.tsx — global dialog, mounted in layout. - useArtistMode.toggleCreation now opens the dialog (isCreationOpen/closeCreation) instead of the redirect — fixes /artists, sidebar, and header at once. TDD: parseSpotifyArtistResults 5/5, addSpotifyArtist 2/2, red→green. tsc clean on touched files, eslint/prettier clean. chat#1867 (/artists add-artist loop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(artists): reuse canonical Spotify types; drop redundant parser (−106 LOC) Simplification pass on the add-artist slice: - Delete lib/spotify/parseSpotifyArtistResults.ts (+ its 5 tests) and the custom SpotifyArtistResult type — types/spotify.ts already exports SpotifySearchResponse ({ artists?: { items: SpotifyArtistSearchResult[] } }) and SpotifyArtistSearchResult (id/name/external_urls/images/followers). useSpotifyArtistSearch now reads `data.artists?.items` directly and returns the canonical type; the component and addSpotifyArtist read images[0].url / followers.total / external_urls.spotify inline. Kept (checked, "no simpler"): addSpotifyArtist's two-step create→link, because POST /api/artists only accepts a name; and useSpotifyArtistSearch, since there is no existing debounced-search hook to reuse. Net -106 LOC; behavior unchanged; addSpotifyArtist 2/2 green, tsc 0 new. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): seed the catalog by kicking /api/valuation on first artist add (chat#1867) (#1879) * feat(onboarding): seed the catalog by kicking /api/valuation on first artist add Closes the onboarding catalog dead-end for direct signups. When the first artist is added via the Spotify search (#1878 already links their Spotify profile), useAddSpotifyArtist now fire-and-forget kicks POST /api/valuation { spotify_artist_id } (api#776) — only when the account has no catalog yet — which materializes the catalog + value band in ~20s. So the "Claim your catalog" step is already complete by the time the user reaches it, and they flow through to the first-task step (weekly report emails). - lib/valuation/runValuation.ts — client POST /api/valuation (TDD 2/2). - hooks/useAddSpotifyArtist.ts — background kick on first add (gated on an empty, loaded catalogs query), surfaced via a toast.promise ("Valuing … → Your catalog is ready"), invalidating the catalogs query on success so the sequence advances. chat#1867 (seed onboarding catalog on first artist add). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(models): fix organizeModels fixture — featured id gpt-5.2 → gpt-5.5 (post-#1877) The featured-models refresh in #1877 replaced openai/gpt-5.2 with openai/gpt-5.5, but organizeModels.test.ts still asserted gpt-5.2 was featured — failing CI on test. Update the fixture + assertion to a currently-featured id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

Why
A brand-new signup gets stuck: the onboarding "Claim your catalog" step links to an empty
/catalogswith no way to create one, so a direct signup can never finish onboarding or reach the first-task step (weekly report emails). Verified 2026-07-22 with two fresh accounts.The pieces to fix it all exist now:
spotify_artist_idon the account.POST /api/valuation, merged + synced totest) turns aspotify_artist_idinto an account-owned catalog + value band in ~20s.What
useAddSpotifyArtistnow fire-and-forget kicksPOST /api/valuation { spotify_artist_id }the moment the first artist is added (only when the account has no catalog yet) — seeding the onboarding catalog in the background. By the time the user reaches "Claim your catalog," it's already (complete), and the sequence flows through to the first-task step.lib/valuation/runValuation.ts— clientPOST /api/valuationwith bearer.hooks/useAddSpotifyArtist.ts— background kick gated on an empty, loaded catalogs query (never fires when a catalog already exists or the query hasn't resolved); surfaced viatoast.promise("Valuing {artist}'s catalog… → Your catalog is ready"), and invalidates["catalogs"]on success so the step advances live.Fire-and-forget: the add dialog closes immediately; the valuation runs in the background. If it fails (e.g. no releases / credits), a soft toast says the catalog can be claimed later — no blocking.
Tests
TDD red→green —
runValuation2/2 (POSTsspotify_artist_idwith bearer; throws on non-ok). tsc clean on touched files; eslint/prettier clean.Verification (pending)
Preview: fresh account → add first artist → confirm a catalog materializes in the background (
account_catalogs) and the onboarding "Claim your catalog" step flips to complete without a manual step. Basetest.Tracking: chat#1867 (seed onboarding catalog on first artist add).
Summary by cubic
Automatically seeds a catalog during onboarding by triggering
POST /api/valuationwhen the first Spotify artist is added, so new signups can complete “Claim your catalog” and proceed to first tasks. Connected tochat#1867; uses the artist’sspotify_artist_idand runs in the background.New Features
spotify_artist_idfrom#1878andPOST /api/valuationfromapi#776.runValuationclient helper (Bearer auth, throws on non-ok) with tests; shows non-blocking toast and invalidates["catalogs"]on success.Bug Fixes
organizeModelstest fixture: update featured id toopenai/gpt-5.5(post-#1877).Written for commit 67366bf. Summary will update on new commits.
Summary by CodeRabbit