Skip to content

feat(artists): add-by-Spotify-id rides POST /api/artists resolve-or-create (chat#1889 row 8) - #1901

Merged
sweetmantech merged 1 commit into
mainfrom
feat/create-artist-canonical
Jul 29, 2026
Merged

feat(artists): add-by-Spotify-id rides POST /api/artists resolve-or-create (chat#1889 row 8)#1901
sweetmantech merged 1 commit into
mainfrom
feat/create-artist-canonical

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

chat leg of the row-8 trilogy on chat#1889. Merge order: docs#278api#793 → this. Depends on api#793 being deployed (falls back gracefully — an api without the param ignores it and behaves exactly as today).

  • createRosterArtist passes spotify_artist_id, returns { artist, created } (created: response.status === 201)
  • addSpotifyArtist drops its client-side profileUrls PATCH — the server attaches the social; the client-side create-then-attach is what minted a duplicate row per signup
  • Avatar written only when created (a reused canonical is shared state, chat#1866); image PATCH non-fatal (chat#1892)
  • useAddRosterArtist call-site updated for the new return shape (file is deleted by chat#1900, which stacks after this conceptually but shares no branch)

TDD red→green (3 failed → green); full chat suite 73 files / 275 tests pass; tsc at the 13 baseline; eslint clean. Preview verification after api#793 merges + test-sync: two accounts adding the same Spotify artist must yield one artist row + two roster links (SQL-asserted).

Tracked in chat#1889 (matrix row 8).

🤖 Generated with Claude Code


Summary by cubic

Add artists by Spotify ID via server-side resolve-or-create to ensure one canonical per Spotify artist and prevent duplicate rows. Drops client-side social patching and only writes the avatar when the artist is newly created.

  • New Features

    • createRosterArtist accepts spotify_artist_id and returns { artist, created } (created is true on HTTP 201; false on 200).
    • addSpotifyArtist passes the ID and only PATCHes the image when created is true; image PATCH errors are ignored.
    • useAddRosterArtist updated to consume the new return shape.
  • Bug Fixes

    • Removed client-side profileUrls PATCH that created duplicate artist rows on signup.
    • Avoids overwriting shared canonical images when linking an existing artist.

Written for commit 3d0a29e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved Spotify artist imports to recognize existing roster artists and prevent duplicate entries.
    • Newly added Spotify artists can now receive their profile image automatically.
    • Artist creation continues successfully even if saving the profile image fails.
    • Improved consistency when adding artists during onboarding, including more reliable roster refresh and error handling.

… (chat#1889 row 8)

createRosterArtist passes spotify_artist_id and reports created (201) vs
canonical-reused (200). addSpotifyArtist drops its client-side
profileUrls PATCH -- the server attaches the social, and create-then-
attach here is what minted a duplicate artist row per signup. The avatar
is written only on 201: a reused canonical is shared state (chat#1866),
and the image PATCH stays non-fatal (chat#1892).

Contract: docs#278. API: api#793. Merge order docs -> api -> this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 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 29, 2026 1:16pm

Request Review

@cursor

cursor Bot commented Jul 29, 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.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1aa34ff8-8cda-4c34-b431-7120f220e91c

📥 Commits

Reviewing files that changed from the base of the PR and between d0913d1 and 3d0a29e.

⛔ Files ignored due to path filters (3)
  • hooks/onboarding/__tests__/useAddRosterArtist.test.tsx is excluded by !**/*.test.* and included by hooks/**
  • lib/artists/__tests__/addSpotifyArtist.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/artists/__tests__/createRosterArtist.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (3)
  • hooks/onboarding/useAddRosterArtist.ts
  • lib/artists/addSpotifyArtist.ts
  • lib/artists/createRosterArtist.ts

📝 Walkthrough

Walkthrough

createRosterArtist now supports Spotify IDs and returns { artist, created }. Spotify imports use this result to avoid profile URL patching and conditionally save a new artist’s avatar. Onboarding adapts to the new return shape.

Changes

Roster artist flow

Layer / File(s) Summary
Create roster artist contract
lib/artists/createRosterArtist.ts
Adds CreateRosterArtistResult, optionally sends spotify_artist_id, and derives created from the HTTP 201 status.
Consumer result handling
lib/artists/addSpotifyArtist.ts, hooks/onboarding/useAddRosterArtist.ts
Updates callers for the wrapped result, removes Spotify profile URL patching, and conditionally saves newly created artists’ avatar images with non-fatal error handling.

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

Sequence Diagram(s)

sequenceDiagram
  participant addSpotifyArtist
  participant createRosterArtist
  participant RosterAPI
  participant saveArtist
  addSpotifyArtist->>createRosterArtist: name, organization id, Spotify artist id
  createRosterArtist->>RosterAPI: POST roster artist payload
  RosterAPI-->>createRosterArtist: artist data and HTTP status
  createRosterArtist-->>addSpotifyArtist: artist and created flag
  addSpotifyArtist->>saveArtist: avatar image when created and available
  saveArtist-->>addSpotifyArtist: update result or error
Loading

Possibly related PRs

Poem

A roster artist finds their place,
With Spotify IDs to guide the chase.
New ones wear an avatar bright,
Existing ones keep their state just right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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.
Solid & Clean Code ✅ Passed The new flow is cohesive: createRosterArtist centralizes the API call, addSpotifyArtist/useAddRosterArtist keep focused orchestration, and nesting/duplication stay low.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/create-artist-canonical

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

ℹ️ 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 on lines +48 to 51
const { artist } = await createRosterArtist(
accessToken,
trimmed,
selectedOrgId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pass Spotify ids from setup roster adds

In the /setup roster flow, the selected Spotify result's id is dropped before this call (AddArtistForm passes only name/profileUrl/image), so createRosterArtist still POSTs only { name, organization_id } and the hook then falls back to the old profileUrls PATCH. That means setup additions never use the new resolve-or-create-by-Spotify-id path and can still mint the duplicate canonical rows this change is trying to avoid; include a spotifyArtistId option here or route setup through addSpotifyArtist.

Useful? React with 👍 / 👎.

Comment on lines +34 to +36
const { artist: updated } = await saveArtist(accessToken, rosterArtist.account_id, {
image: imageUrl,
});

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 Keep the social fallback for old artist APIs

Because this patch also changed the follow-up PATCH to contain only image, a client pointed at an API that ignores the new spotify_artist_id field will still get a successful create response but will no longer send the old profileUrls.SPOTIFY update. In that compatibility window the artist is added without any Spotify social, so catalog/enrichment flows that resolve Spotify from connected socials fail; either keep the old social PATCH until the API capability is guaranteed or gate on a response that proves the server attached the social.

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 6 files

Confidence score: 3/5

  • In hooks/onboarding/useAddRosterArtist.ts, createRosterArtist is still called without spotifyArtistId, so the new resolve-or-create path may not run and onboarding can create incomplete/duplicate artist records; pass the Spotify ID through this call site and add a focused flow test around that branch.
  • In lib/artists/addSpotifyArtist.ts, compatibility with older API deployments is fragile: artist creation can appear successful while silently dropping Spotify social linkage when spotify_artist_id is unsupported, which risks confusing users and inconsistent data; add a capability-aware fallback (or enforce a coordinated API minimum version) so unsupported servers fail clearly or recover correctly.
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/onboarding/useAddRosterArtist.ts">

<violation number="1" location="hooks/onboarding/useAddRosterArtist.ts:48">
P2: The updated call site here still only passes `accessToken`, `trimmed`, and `selectedOrgId` to `createRosterArtist`, with no `spotifyArtistId` argument. Since `createRosterArtist` now supports resolve-or-create by Spotify id specifically to avoid minting duplicate canonical artist rows, the `/setup` roster flow (which has access to a Spotify id via `AddArtistForm`) will keep using the name-only path and can still create duplicate rows — the exact issue this PR is meant to fix.</violation>
</file>

<file name="lib/artists/addSpotifyArtist.ts">

<violation number="1" location="lib/artists/addSpotifyArtist.ts:23">
P2: When this client talks to an API deployment that does not yet support `spotify_artist_id`, adding a Spotify artist succeeds but its Spotify social is silently missing. Retaining a capability-aware fallback (or requiring the API deployment before using this client) would preserve the documented backward-compatible behavior without reintroducing duplicate creation.</violation>
</file>

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

Re-trigger cubic

throw new Error("Please sign in to add an artist");
}
const artist = await createRosterArtist(
const { artist } = await createRosterArtist(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The updated call site here still only passes accessToken, trimmed, and selectedOrgId to createRosterArtist, with no spotifyArtistId argument. Since createRosterArtist now supports resolve-or-create by Spotify id specifically to avoid minting duplicate canonical artist rows, the /setup roster flow (which has access to a Spotify id via AddArtistForm) will keep using the name-only path and can still create duplicate rows — the exact issue this PR is meant to fix.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/onboarding/useAddRosterArtist.ts, line 48:

<comment>The updated call site here still only passes `accessToken`, `trimmed`, and `selectedOrgId` to `createRosterArtist`, with no `spotifyArtistId` argument. Since `createRosterArtist` now supports resolve-or-create by Spotify id specifically to avoid minting duplicate canonical artist rows, the `/setup` roster flow (which has access to a Spotify id via `AddArtistForm`) will keep using the name-only path and can still create duplicate rows — the exact issue this PR is meant to fix.</comment>

<file context>
@@ -45,7 +45,7 @@ export function useAddRosterArtist() {
         throw new Error("Please sign in to add an artist");
       }
-      const artist = await createRosterArtist(
+      const { artist } = await createRosterArtist(
         accessToken,
         trimmed,
</file context>


const imageUrl = artist.images?.[0]?.url;
const { artist: updated } = await saveArtist(
const { artist: rosterArtist, created } = await createRosterArtist(

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: When this client talks to an API deployment that does not yet support spotify_artist_id, adding a Spotify artist succeeds but its Spotify social is silently missing. Retaining a capability-aware fallback (or requiring the API deployment before using this client) would preserve the documented backward-compatible behavior without reintroducing duplicate creation.

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

<comment>When this client talks to an API deployment that does not yet support `spotify_artist_id`, adding a Spotify artist succeeds but its Spotify social is silently missing. Retaining a capability-aware fallback (or requiring the API deployment before using this client) would preserve the documented backward-compatible behavior without reintroducing duplicate creation.</comment>

<file context>
@@ -4,27 +4,38 @@ import { createRosterArtist } from "@/lib/artists/createRosterArtist";
-
-  const imageUrl = artist.images?.[0]?.url;
-  const { artist: updated } = await saveArtist(
+  const { artist: rosterArtist, created } = await createRosterArtist(
     accessToken,
-    created.account_id,
</file context>

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification — the new contract live from the client, cross-account reuse visible in the UI

  • Preview: chat-gle854q1o-recoup.vercel.app (built from PR head 3d0a29e8), against test-recoup-api freshly synced with api#793 (sync verified three ways: Vercel CLI deployment timestamp, GitHub deployment sha f652dc68, and behaviorally — the alias 400s on an empty spotify_artist_id, a validator that only exists in the new code)
  • Account: brand-new sweetman+july292026926@…, empty roster

The changed surface (/artists dialog → useAddSpotifyArtist) — instrumented network log

Added Grace Ives, whose single canonical (0d5e37f8…) was minted this morning under a different account during api#793's verification:

POST /api/artists   {"name":"Grace Ives","spotify_artist_id":"4TZieE5978SbTInJswaay2"}   → 200
GET  /api/artists?                                                                        → 200
POST /api/valuation {"spotify_artist_id":"4TZieE5978SbTInJswaay2"}                        (seeding, account had no catalog)
Assertion Result
POST carries spotify_artist_id ✅ in the body verbatim
Response 200 (reuse, not create)
Zero client PATCH with profileUrls anyProfileUrlsPatch: false across the whole log
Zero image PATCH on the reused canonical ✅ — created: false path, chat#1866 shared-state guard working
Rostered artist IS the cross-account canonical GET /api/artists returns 0d5e37f8… with its server-attached social
SQL ground truth 1 Grace Ives artist row globally, 2 roster links (the other account + this one)

roster

Grace Ives renders a placeholder avatar — this is correct, not a bug: her canonical was created via curl with no image, and this PR deliberately refuses to write the adding account's image onto a reused shared canonical. A safe follow-up if wanted: write the image when the canonical has none (filling a blank is not vandalising shared state).

Also observed, expected: the onboarding form still rides the old path on this preview

/setup/artistsAddArtistForm still calls useAddRosterArtist (POST name-only + PATCH profileUrls) — because the swap of that form to useAddSpotifyArtist is chat#1900, which is in draft. Until #1900 merges, onboarding adds still create per-signup rows (which the valuation then reuses server-side, so rosters stay clean — but table-level dedup for onboarding waits on #1900). Recommend merging #1900 promptly after this to close that window; I'll re-verify it on top of the trilogy first.

Suite recap

TDD red→green; chat suite 73 files / 275 tests; tsc at the 13 baseline; all four CI checks green.

Ready to merge. With this in, row 8's slice is complete; database#49 + api#794 close rows 9–10.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Follow-up on the placeholder-avatar note: tracked as chat#1889 matrix row 24, and the premise narrowed on investigation — Grace Ives' canonical self-healed ~30s after the screenshot: the seeded valuation's enrichSearchedArtistProfile backfilled her image (SQL-verified: account_info.image now set). So the remaining gap is only (a) accounts that already have a catalog, where seeding doesn't fire, and (b) the interim window. Slotted in the display-quality cluster next to the handle/follower row, not above the revenue path.

@sweetmantech
sweetmantech merged commit e74a7bc into main Jul 29, 2026
4 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