Skip to content

feat: roster + socials verification onboarding steps (chat#1867) - #1870

Merged
sweetmantech merged 3 commits into
testfrom
feat/onboarding-roster-socials-steps
Jul 21, 2026
Merged

feat: roster + socials verification onboarding steps (chat#1867)#1870
sweetmantech merged 3 commits into
testfrom
feat/onboarding-roster-socials-steps

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Part of #1867 — the "Roster + socials verification steps" item.

What

Two self-contained onboarding step components, mounted standalone at /onboarding/roster so this slice is user-testable today. The state-derived onboarding router (sibling PR, feat/onboarding-router) will mount ConfirmRosterStep / VerifySocialsStep directly with its own checkpoint-derived stepping — nothing here depends on that branch.

Step 1 — Confirm your roster (ConfirmRosterStep)

  • Lists the artist(s) the valuation flow auto-created (name, image, matched-socials count) from the shared ArtistProvider roster.
  • "Add another artist" inline form for multi-artist managers (CVLT / Epitaph case) — creates via the existing POST /api/artists endpoint (Privy bearer auth) and refreshes the shared roster. The existing in-app add-artist path routes through a chat prompt (/?q=create a new artist), which would navigate away mid-sequence, so the step calls the same API the platform already exposes instead.
  • Confirm advances; disabled on an empty roster.

Step 2 — Verify socials (VerifySocialsStep)

  • Per rostered artist: every auto-matched social with platform, handle, and follower count (rendered from the roster payload's socials rows).
  • Each match is Correct / Wrong. Marking one wrong (a known auto-match failure mode) opens a paste-the-right-link fix that goes through the existing PATCH /api/artists/{id} profileUrls path, replaces the platform's social, refreshes the roster, and auto-confirms the replacement.
  • Artists with zero (or zero correct) socials record an explicit "This artist has no socials".
  • Continue unlocks only when every artist has ≥1 confirmed social or an explicit none — matching the issue's done-when.

How

  • All confirmation/resolution logic is pure and TDD'd in lib/onboarding/ (verdict reducers, per-artist + step resolution predicates, PATCH payload builder with APPPLEAPPLE platform-key normalization, follower-count accessor tolerant of the API's snake_case rows behind chat's camelCase SOCIAL type).
  • No new endpoints, no new tables, no new context providers — hooks compose the existing ArtistProvider, OrganizationProvider, and Privy auth, mirroring useArtistsRoster / createTask patterns.
  • One exported function per file, all files <100 LOC.

Verification

Local (all run in this worktree):

  • pnpm exec vitest run43 files / 168 tests passed (38 new tests across 8 new suites, written red-first).
  • pnpm exec tsc --noEmit — clean (exit 0).
  • pnpm exec eslint on all new files — clean. (pnpm lint = next lint is broken repo-wide on Next 16 — "Invalid project directory ... /lint" — unrelated to this PR.)

Preview verification pending. How to test on the preview:

  1. Sign in as an account with a valuation-created artist (or any rostered account) and open /onboarding/roster.
  2. Step 1: roster shows the auto-created artist(s); "Add another artist" → name → Add appears in the list (and in the sidebar roster); confirm advances.
  3. Step 2: each artist card lists its socials with handle + follower count. Mark one Wrong, paste the correct profile URL, Save → the social is replaced and shows confirmed; a bogus URL (e.g. example.com/x) is rejected with a toast. "This artist has no socials" resolves an artist without matches. Continue stays disabled until every artist is resolved, then completes to the done panel.
  4. Confirm the fix persisted: the artist's socials in settings / account_socials reflect the corrected link.

🤖 Generated with Claude Code


Summary by cubic

Adds onboarding steps to confirm the roster and verify socials at /onboarding/roster. Implements chat#1867 with an accept-by-default socials step so users can add artists, add missing profiles, and fix wrong links without blocking progress.

  • New Features

    • ConfirmRosterStep: shows auto-created artists, inline “Add another artist” via POST /api/artists; confirm disabled when the roster is empty.
    • VerifySocialsStep: lists matched profiles with handle + follower count; Edit to paste a correct link updates via PATCH /api/artists/{id} profileUrls; “Add a profile” is available for every artist; Continue always proceeds.
    • Standalone flow at /onboarding/roster with a done panel.
  • Refactors

    • Simplified verify-socials per design: removed Correct/Wrong verdicts, “no socials” toggle, and gating logic; matches are accepted by default.
    • No new endpoints or providers; hooks reuse ArtistProvider, OrganizationProvider, and Privy auth; added helpers for APPPLE→APPLE normalization and follower-count reading across API shapes.

Written for commit 80d8734. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Introduced an onboarding roster step to review your artist roster and verify associated socials.
    • Added an “add another artist” option, auto-matched socials display, and the ability to correct social/profile links.
    • Included a roster-verified confirmation panel with a return-to-home action.
  • Bug Fixes
    • Improved social platform normalization (including legacy name variations).
    • Enhanced follower count parsing to support multiple data formats.
    • Added clearer validation and feedback for unsupported or invalid profile links.

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

cursor Bot commented Jul 20, 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 20, 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 21, 2026 7:41pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 20, 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

Run ID: 2306cc74-2d1e-4f7e-aeb8-e221088fc956

📥 Commits

Reviewing files that changed from the base of the PR and between 65cd47a and 80d8734.

📒 Files selected for processing (1)
  • components/Onboarding/ArtistSocialsCard.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/Onboarding/ArtistSocialsCard.tsx

📝 Walkthrough

Walkthrough

Changes

Roster and social verification onboarding

Layer / File(s) Summary
Onboarding data contracts and API helpers
lib/onboarding/*, lib/artists/createRosterArtist.ts
Adds social payload and platform normalization helpers, follower-count normalization, and authenticated roster artist creation.
Roster review and artist addition
components/Onboarding/RosterArtistRow.tsx, components/Onboarding/AddArtistForm.tsx, components/Onboarding/ConfirmRosterStep.tsx, hooks/onboarding/useAddRosterArtist.ts
Displays non-workspace artists, supports adding artists, refreshes the roster, and confirms the roster step.
Social profile verification and correction
components/Onboarding/SocialFixForm.tsx, components/Onboarding/SocialRow.tsx, components/Onboarding/ArtistSocialsCard.tsx, components/Onboarding/VerifySocialsStep.tsx, hooks/onboarding/useSocialFix.ts
Displays matched socials and supports adding or correcting profile links with persisted updates.
Onboarding flow orchestration and completion
components/Onboarding/RosterSocialsFlow.tsx, components/Onboarding/RosterVerifiedPanel.tsx, app/onboarding/roster/page.tsx
Routes users through roster review, social verification, and the final completion panel.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RosterSocialsFlow
  participant ConfirmRosterStep
  participant VerifySocialsStep
  participant RosterVerifiedPanel
  User->>RosterSocialsFlow: open roster onboarding
  RosterSocialsFlow->>ConfirmRosterStep: render roster step
  User->>ConfirmRosterStep: confirm roster
  ConfirmRosterStep-->>RosterSocialsFlow: onConfirmed()
  RosterSocialsFlow->>VerifySocialsStep: render socials step
  User->>VerifySocialsStep: continue after verification
  VerifySocialsStep-->>RosterSocialsFlow: onConfirmed()
  RosterSocialsFlow->>RosterVerifiedPanel: render completion panel
Loading

Poem

Artists gather, profiles align,
Links are polished, one by one.
A roster steps through social light,
Then green checks mark the work done right.
Back to Recoup—the flow takes flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Several new components/hooks exceed the 20-line guideline (e.g. ConfirmRosterStep, ArtistSocialsCard, SocialRow, useSocialFix) and duplicate form logic. Split JSX-heavy components into smaller presentational pieces and extract a shared async text-input form/helper for AddArtistForm and SocialFixForm.
✅ 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/onboarding-roster-socials-steps

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.

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Local + CI verification snapshot (2026-07-20):

Gate Command Documented expectation Actual result
Unit tests (local) pnpm exec vitest run All suites green, incl. 8 new red-first suites 43 files / 168 tests passed (38 new)
Unit tests (CI) pnpm test on PR Pass Pass (52s)
Types pnpm exec tsc --noEmit Clean Exit 0, no errors
Lint pnpm exec eslint <new files> Clean No findings (pnpm lint = next lint is broken repo-wide on Next 16, pre-existing)
Production build Vercel preview deployment Compiles + deploys Pass ("Deployment has completed")

Preview flow verification (signed-in walkthrough of /onboarding/roster: confirm roster, add artist, wrong-match fix via PATCH, explicit none, gated continue) still pending — steps in the PR description.

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

ℹ️ 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".

socialIds: string[],
): boolean {
if (!verification) return false;
if (verification.none) return true;

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 Clear matched socials before accepting none

When an artist has auto-matched profiles but the user chooses “This artist has no socials” for the zero-correct case, this branch resolves the artist without any PATCH/removal of the existing account_socials. Because the none flag is only local onboarding state, a reload and downstream reports/tasks still see the old matched profiles; require the current socials to be empty, or clear them server-side, before treating this as resolved.

Useful? React with 👍 / 👎.

Comment on lines +17 to +20
const verdicts = socialIds.map((id) => verification.verdicts[id]);
const allDecided = verdicts.every((verdict) => verdict !== undefined);
const anyConfirmed = verdicts.some((verdict) => verdict === "confirmed");
return allDecided && anyConfirmed;

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 Require fixing rejected socials before continuing

When an artist has multiple matches and only some are wrong (for example, Instagram is correct but TikTok is a bad auto-match), this returns true once every row has a verdict because one confirmed social is enough. Rejecting a row is client-only, so the rejected account_socials row remains attached and downstream reports/tasks can still consume the wrong profile; require no current rejected verdicts, or a successful fix/removal, before resolving the artist.

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.

6 issues found across 31 files

Confidence score: 3/5

  • hooks/onboarding/useAddRosterArtist.ts currently treats “create succeeded + refresh failed” as a full failure, leaving the form populated so a retry can create a duplicate artist; this is the highest user-impact risk in the PR. Split creation success handling from refresh errors (or make retries idempotent) before merging.
  • hooks/onboarding/useSocialFix.ts uses shared loading state across artists, so concurrent fixes can re-enable a still-pending form and allow duplicate PATCHes while clearing the wrong artist’s pending state. Track pending work per artist ID (or keep all fix forms disabled until all requests settle) before merge.
  • components/Onboarding/SocialFixForm.tsx has an async submit path that doesn’t catch onSubmit rejections, which can leave submission state stuck and produce unhandled promise errors. Wrap submission in try/catch/finally so errors are surfaced and state is always reset.
  • Lower-severity polish remains in components/Onboarding/AddArtistForm.tsx and lib/artists/createRosterArtist.ts: keyboard focus and loading-label accessibility regressions hurt usability, and JSON parse failures can mask real API errors. These are good to address now for onboarding reliability, but are less merge-blocking than the duplicate-action paths above.
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="components/Onboarding/AddArtistForm.tsx">

<violation number="1" location="components/Onboarding/AddArtistForm.tsx:49">
P3: While submission is pending, the button has no accessible name because `Add` is replaced by the spinner icon. Keep an accessible loading label so screen-reader users receive the operation state.</violation>

<violation number="2" location="components/Onboarding/AddArtistForm.tsx:55">
P3: Keyboard focus is lost when Cancel closes the form because the focused button unmounts without returning focus to the trigger. Restore focus to `Add another artist` after closing so keyboard users can continue navigation predictably.</violation>
</file>

<file name="hooks/onboarding/useSocialFix.ts">

<violation number="1" location="hooks/onboarding/useSocialFix.ts:36">
P2: Concurrent fixes for different artists can re-enable a still-pending form and permit duplicate PATCHes; completion of either request also clears the other artist’s loading state. Track pending IDs (or disable all fix forms) until every outstanding request finishes.</violation>
</file>

<file name="components/Onboarding/SocialFixForm.tsx">

<violation number="1" location="components/Onboarding/SocialFixForm.tsx:22">
P2: The async `handleSubmit` doesn't catch promise rejections from `onSubmit`. If a future consumer or bug causes a thrown error, the rejection goes unhandled and `isSubmitting` (controlled by parent) may never reset. Wrap in a try/catch as a defensive measure.</violation>
</file>

<file name="hooks/onboarding/useAddRosterArtist.ts">

<violation number="1" location="hooks/onboarding/useAddRosterArtist.ts:31">
P2: A successful POST is reported as a failed add when the subsequent roster refresh fails. The form remains populated and retrying creates a duplicate artist; handle refresh failure separately from creation success (or insert the returned artist into the roster cache).</violation>
</file>

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

<violation number="1" location="lib/artists/createRosterArtist.ts:33">
P3: Non-JSON or empty failed responses surface a JSON parse error to the onboarding toast instead of a useful creation failure. Catch JSON parsing here and preserve a stable error message for failed responses.</violation>
</file>

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

Re-trigger cubic

throw new Error("Please sign in to add an artist");
}
await createRosterArtist(accessToken, trimmed, selectedOrgId);
await getArtists();

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: A successful POST is reported as a failed add when the subsequent roster refresh fails. The form remains populated and retrying creates a duplicate artist; handle refresh failure separately from creation success (or insert the returned artist into the roster cache).

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

<comment>A successful POST is reported as a failed add when the subsequent roster refresh fails. The form remains populated and retrying creates a duplicate artist; handle refresh failure separately from creation success (or insert the returned artist into the roster cache).</comment>

<file context>
@@ -0,0 +1,44 @@
+        throw new Error("Please sign in to add an artist");
+      }
+      await createRosterArtist(accessToken, trimmed, selectedOrgId);
+      await getArtists();
+      return true;
+    } catch (error) {
</file context>

);
return false;
}
setFixingArtistId(artist.account_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: Concurrent fixes for different artists can re-enable a still-pending form and permit duplicate PATCHes; completion of either request also clears the other artist’s loading state. Track pending IDs (or disable all fix forms) until every outstanding request finishes.

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

<comment>Concurrent fixes for different artists can re-enable a still-pending form and permit duplicate PATCHes; completion of either request also clears the other artist’s loading state. Track pending IDs (or disable all fix forms) until every outstanding request finishes.</comment>

<file context>
@@ -0,0 +1,65 @@
+      );
+      return false;
+    }
+    setFixingArtistId(artist.account_id);
+    try {
+      const accessToken = await getAccessToken();
</file context>

Comment thread hooks/onboarding/useSocialsVerification.ts Outdated
Comment on lines +22 to +26
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const saved = await onSubmit(url);
if (saved) setUrl("");
};

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 async handleSubmit doesn't catch promise rejections from onSubmit. If a future consumer or bug causes a thrown error, the rejection goes unhandled and isSubmitting (controlled by parent) may never reset. Wrap in a try/catch as a defensive measure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SocialFixForm.tsx, line 22:

<comment>The async `handleSubmit` doesn't catch promise rejections from `onSubmit`. If a future consumer or bug causes a thrown error, the rejection goes unhandled and `isSubmitting` (controlled by parent) may never reset. Wrap in a try/catch as a defensive measure.</comment>

<file context>
@@ -0,0 +1,44 @@
+}: SocialFixFormProps) => {
+  const [url, setUrl] = useState("");
+
+  const handleSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    const saved = await onSubmit(url);
</file context>
Suggested change
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const saved = await onSubmit(url);
if (saved) setUrl("");
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const saved = await onSubmit(url);
if (saved) setUrl("");
} catch {
// Parent handles display; keep input for retry
}
};

Comment thread components/Onboarding/SocialVerifyRow.tsx Outdated
type="button"
variant="ghost"
disabled={isAdding}
onClick={() => setIsOpen(false)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Keyboard focus is lost when Cancel closes the form because the focused button unmounts without returning focus to the trigger. Restore focus to Add another artist after closing so keyboard users can continue navigation predictably.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/AddArtistForm.tsx, line 55:

<comment>Keyboard focus is lost when Cancel closes the form because the focused button unmounts without returning focus to the trigger. Restore focus to `Add another artist` after closing so keyboard users can continue navigation predictably.</comment>

<file context>
@@ -0,0 +1,63 @@
+        type="button"
+        variant="ghost"
+        disabled={isAdding}
+        onClick={() => setIsOpen(false)}
+      >
+        Cancel
</file context>

aria-label="Artist name"
/>
<Button type="submit" disabled={isAdding || !name.trim()}>
{isAdding ? <Loader className="size-4 animate-spin" /> : "Add"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: While submission is pending, the button has no accessible name because Add is replaced by the spinner icon. Keep an accessible loading label so screen-reader users receive the operation state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/AddArtistForm.tsx, line 49:

<comment>While submission is pending, the button has no accessible name because `Add` is replaced by the spinner icon. Keep an accessible loading label so screen-reader users receive the operation state.</comment>

<file context>
@@ -0,0 +1,63 @@
+        aria-label="Artist name"
+      />
+      <Button type="submit" disabled={isAdding || !name.trim()}>
+        {isAdding ? <Loader className="size-4 animate-spin" /> : "Add"}
+      </Button>
+      <Button
</file context>

body: JSON.stringify(body),
});

const data: CreateArtistResponse = await response.json();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Non-JSON or empty failed responses surface a JSON parse error to the onboarding toast instead of a useful creation failure. Catch JSON parsing here and preserve a stable error message for failed responses.

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

<comment>Non-JSON or empty failed responses surface a JSON parse error to the onboarding toast instead of a useful creation failure. Catch JSON parsing here and preserve a stable error message for failed responses.</comment>

<file context>
@@ -0,0 +1,40 @@
+    body: JSON.stringify(body),
+  });
+
+  const data: CreateArtistResponse = await response.json();
+
+  if (!response.ok || !data.artist) {
</file context>

@sweetmantech
sweetmantech changed the base branch from main to test July 21, 2026 11:46
@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification — happy path · preview chat-7n0ojol2r-recoup.vercel.app (sha-verified against PR head 7c886168), Chrome DevTools MCP. Signed in as the same fresh account from the #1873 test — sweetmantech+jul2120261002@ (cd59ed42), which owns one valuation-created artist (Del Water Gap, 1 auto-matched Spotify social) — opened /onboarding/roster.

# Check (#1870 spec) Actual Result
1 Step 1 lists the valuation-created artist(s) w/ matched-socials count "Confirm your roster" · Del Water Gap · "1 social profile matched"; copy correctly singular ("this artist")
2 Continue disabled on empty roster "This is my artist — continue" enabled with 1 artist (disabled state confirmed pre-load)
3 "Add another artist" → POST /api/artists, appears in roster Added "QA Roster Artist" → row shows ("No socials matched yet"); copy/button pluralized ("these artists"). DB: new account_artist_ids row for cd59ed42
4 Step 2 lists each artist's socials w/ handle + follower count Del Water Gap → Spotify @artist · 0 followers (Correct/Wrong); QA Roster Artist → "No socials were auto-matched"
5 Wrong → paste-link fix; bogus URL rejected Marked Wrong → fix form; example.com/not-a-spotify-linkrejected, social unchanged, not saved
6 Valid link → PATCH /api/artists/{id} → replaced + auto-confirmed Pasted a valid Spotify artist URL → Save → social shows "Verified", "Correct" auto-pressed; persists in account_socials pointing at the correct Spotify artist
7 "This artist has no socials" resolves an unmatched artist QA Roster Artist → "No socials" badge
8 Continue gated until every artist resolved Stayed disabled with 1 of 2 resolved; enabled only once both were
9 Completes to a done panel "Roster verified" panel + "Back to Recoup"
10 Console clean zero errors

10/10. Both done-when conditions met — multi-artist rostering works, and each artist exits with ≥1 confirmed social or an explicit "none". The APPPLEAPPLE / snake-case follower-count handling didn't surface any rendering issues on this Spotify-social account.

Step 1 — confirm roster (add-artist added a 2nd):
step1

Step 2 — verify socials (1-social + 0-social cases):
step2

Step 2 — both resolved (Verified + No socials), Continue enabled:
step2 resolved

Done:
done

Test data note: I added "QA Roster Artist" to cd59ed42's roster to exercise multi-artist + the no-socials path; left in place (harmless test-account data). The social "fix" used Del Water Gap's canonical Spotify URL, so the stored value is unchanged but the PATCH round-trip + auto-verify completed.

…hat#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>

@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 (4)
components/Onboarding/RosterArtistRow.tsx (1)

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

Export named component prop interfaces.

Define and export RosterArtistRowProps and ConfirmRosterStepProps instead of inline object types.

  • components/Onboarding/RosterArtistRow.tsx#L6-L6: add and use export interface RosterArtistRowProps.
  • components/Onboarding/ConfirmRosterStep.tsx#L14-L14: add and use export interface ConfirmRosterStepProps.

As per coding guidelines, “Export component prop types with explicit ComponentNameProps naming convention”; as per path instructions, “Use TypeScript interfaces for props.”

🤖 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/Onboarding/RosterArtistRow.tsx` at line 6, Replace the inline
props type in the RosterArtistRow component with an exported interface named
RosterArtistRowProps and use it in the component declaration. Apply the same
change in components/Onboarding/ConfirmRosterStep.tsx at line 14 by defining and
using the exported ConfirmRosterStepProps interface.

Sources: Coding guidelines, Path instructions

components/Onboarding/SocialFixForm.tsx (3)

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

Use Radix icons for these UI controls.

  • components/Onboarding/SocialFixForm.tsx#L4-L4: replace the loading control icon with the corresponding @radix-ui/react-icons icon.
  • components/Onboarding/SocialRow.tsx#L4-L4: replace the edit control icon with the corresponding @radix-ui/react-icons icon.

As per coding guidelines, “Use @radix-ui/react-icons for UI component icons (not Lucide React).”

🤖 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/Onboarding/SocialFixForm.tsx` at line 4, Replace the Lucide
loading icon import and usage in components/Onboarding/SocialFixForm.tsx at line
4 with the corresponding `@radix-ui/react-icons` icon. Also replace the edit
control icon import and usage in components/Onboarding/SocialRow.tsx at line 4
with its corresponding Radix icon, preserving the existing control behavior.

Source: Coding guidelines


29-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expose component slots and state consistently.

Add a descriptive data-slot to each component root. Also expose the existing loading, editing, and adding states through data-state where applicable.

  • components/Onboarding/SocialFixForm.tsx#L29-L40: add a form slot and loading/idle state.
  • components/Onboarding/SocialRow.tsx#L44-L64: add a row slot and open/closed editing state.
  • components/Onboarding/ArtistSocialsCard.tsx#L36-L77: add a card slot and adding/idle state.
  • components/Onboarding/VerifySocialsStep.tsx#L20-L45: add a step slot.

As per coding guidelines, “Use data-slot attribute for component identification” and “Use data-state attribute for visual component states (open/closed, loading, etc.).”

🤖 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/Onboarding/SocialFixForm.tsx` around lines 29 - 40, Expose
consistent component metadata across the four affected files: in
components/Onboarding/SocialFixForm.tsx lines 29-40, add a form data-slot and
loading/idle data-state; in components/Onboarding/SocialRow.tsx lines 44-64, add
a row data-slot and open/closed editing data-state; in
components/Onboarding/ArtistSocialsCard.tsx lines 36-77, add a card data-slot
and adding/idle data-state; and in components/Onboarding/VerifySocialsStep.tsx
lines 20-45, add a step data-slot. Use each component’s existing state symbols
without changing behavior.

Source: Coding guidelines


8-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export named component prop contracts.

  • components/Onboarding/SocialFixForm.tsx#L8-L12: change SocialFixFormProps to an exported interface.
  • components/Onboarding/SocialRow.tsx#L13-L17: change SocialRowProps to an exported interface.
  • components/Onboarding/ArtistSocialsCard.tsx#L9-L13: change ArtistSocialsCardProps to an exported interface.
  • components/Onboarding/VerifySocialsStep.tsx#L14-L14: introduce and export VerifySocialsStepProps instead of using an inline prop type.

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

🤖 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/Onboarding/SocialFixForm.tsx` around lines 8 - 12, Export the
named prop contracts in components/Onboarding/SocialFixForm.tsx:8-12,
components/Onboarding/SocialRow.tsx:13-17, and
components/Onboarding/ArtistSocialsCard.tsx:9-13 by changing SocialFixFormProps,
SocialRowProps, and ArtistSocialsCardProps to exported interfaces. In
components/Onboarding/VerifySocialsStep.tsx:14, replace the inline prop type
with a new exported VerifySocialsStepProps interface, preserving all existing
prop fields and component behavior.

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 `@components/Onboarding/AddArtistForm.tsx`:
- Around line 48-50: The submit Button in AddArtistForm currently replaces its
text label with only a loader while isAdding; preserve a reliable accessible
label and loading status by retaining or providing text such as “Adding…”
alongside the loader, while keeping the existing disabled behavior.

---

Nitpick comments:
In `@components/Onboarding/RosterArtistRow.tsx`:
- Line 6: Replace the inline props type in the RosterArtistRow component with an
exported interface named RosterArtistRowProps and use it in the component
declaration. Apply the same change in
components/Onboarding/ConfirmRosterStep.tsx at line 14 by defining and using the
exported ConfirmRosterStepProps interface.

In `@components/Onboarding/SocialFixForm.tsx`:
- Line 4: Replace the Lucide loading icon import and usage in
components/Onboarding/SocialFixForm.tsx at line 4 with the corresponding
`@radix-ui/react-icons` icon. Also replace the edit control icon import and usage
in components/Onboarding/SocialRow.tsx at line 4 with its corresponding Radix
icon, preserving the existing control behavior.
- Around line 29-40: Expose consistent component metadata across the four
affected files: in components/Onboarding/SocialFixForm.tsx lines 29-40, add a
form data-slot and loading/idle data-state; in
components/Onboarding/SocialRow.tsx lines 44-64, add a row data-slot and
open/closed editing data-state; in components/Onboarding/ArtistSocialsCard.tsx
lines 36-77, add a card data-slot and adding/idle data-state; and in
components/Onboarding/VerifySocialsStep.tsx lines 20-45, add a step data-slot.
Use each component’s existing state symbols without changing behavior.
- Around line 8-12: Export the named prop contracts in
components/Onboarding/SocialFixForm.tsx:8-12,
components/Onboarding/SocialRow.tsx:13-17, and
components/Onboarding/ArtistSocialsCard.tsx:9-13 by changing SocialFixFormProps,
SocialRowProps, and ArtistSocialsCardProps to exported interfaces. In
components/Onboarding/VerifySocialsStep.tsx:14, replace the inline prop type
with a new exported VerifySocialsStepProps interface, preserving all existing
prop fields and component behavior.
🪄 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: b60784df-5762-4bbe-9705-cd257070c516

📥 Commits

Reviewing files that changed from the base of the PR and between 9491bd2 and 65cd47a.

⛔ Files ignored due to path filters (3)
  • lib/artists/__tests__/createRosterArtist.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/onboarding/__tests__/buildSocialFixPayload.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/onboarding/__tests__/getSocialFollowerCount.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (16)
  • app/onboarding/roster/page.tsx
  • components/Onboarding/AddArtistForm.tsx
  • components/Onboarding/ArtistSocialsCard.tsx
  • components/Onboarding/ConfirmRosterStep.tsx
  • components/Onboarding/RosterArtistRow.tsx
  • components/Onboarding/RosterSocialsFlow.tsx
  • components/Onboarding/RosterVerifiedPanel.tsx
  • components/Onboarding/SocialFixForm.tsx
  • components/Onboarding/SocialRow.tsx
  • components/Onboarding/VerifySocialsStep.tsx
  • hooks/onboarding/useAddRosterArtist.ts
  • hooks/onboarding/useSocialFix.ts
  • lib/artists/createRosterArtist.ts
  • lib/onboarding/buildSocialFixPayload.ts
  • lib/onboarding/getSocialFollowerCount.ts
  • lib/onboarding/normalizeSocialPlatform.ts

Comment on lines +48 to +50
<Button type="submit" disabled={isAdding || !name.trim()}>
{isAdding ? <Loader className="size-4 animate-spin" /> : "Add"}
</Button>

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 | 🟡 Minor | ⚡ Quick win

Keep an accessible submit label while loading.

Line 49 replaces “Add” with an icon, leaving the disabled submit control without a reliable accessible name or status.

Proposed fix
-      <Button type="submit" disabled={isAdding || !name.trim()}>
-        {isAdding ? <Loader className="size-4 animate-spin" /> : "Add"}
+      <Button type="submit" disabled={isAdding || !name.trim()}>
+        {isAdding ? (
+          <>
+            <Loader aria-hidden="true" className="size-4 animate-spin" />
+            <span className="sr-only">Adding artist</span>
+          </>
+        ) : (
+          "Add"
+        )}
       </Button>

As per coding guidelines, “Provide clear labels and error messages in form components.”

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

Suggested change
<Button type="submit" disabled={isAdding || !name.trim()}>
{isAdding ? <Loader className="size-4 animate-spin" /> : "Add"}
</Button>
<Button type="submit" disabled={isAdding || !name.trim()}>
{isAdding ? (
<>
<Loader aria-hidden="true" className="size-4 animate-spin" />
<span className="sr-only">Adding artist</span>
</>
) : (
"Add"
)}
</Button>
🤖 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/Onboarding/AddArtistForm.tsx` around lines 48 - 50, The submit
Button in AddArtistForm currently replaces its text label with only a loader
while isAdding; preserve a reliable accessible label and loading status by
retaining or providing text such as “Adding…” alongside the loader, while
keeping the existing disabled behavior.

Source: Coding guidelines

@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="components/Onboarding/SocialRow.tsx">

<violation number="1" location="components/Onboarding/SocialRow.tsx:71">
P2: Editing a wrong match to a profile on a different platform leaves the original wrong social in the roster. Pass the edited social/platform through this callback and include an empty value for its old platform when it differs from the corrected URL's platform.</violation>
</file>

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

Re-trigger cubic

<SocialFixForm
placeholder="Paste the correct profile link"
isSubmitting={isSubmitting}
onSubmit={handleFix}

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: Editing a wrong match to a profile on a different platform leaves the original wrong social in the roster. Pass the edited social/platform through this callback and include an empty value for its old platform when it differs from the corrected URL's platform.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SocialRow.tsx, line 71:

<comment>Editing a wrong match to a profile on a different platform leaves the original wrong social in the roster. Pass the edited social/platform through this callback and include an empty value for its old platform when it differs from the corrected URL's platform.</comment>

<file context>
@@ -0,0 +1,79 @@
+          <SocialFixForm
+            placeholder="Paste the correct profile link"
+            isSubmitting={isSubmitting}
+            onSubmit={handleFix}
+          />
+        </div>
</file context>

…y 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>

@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 1 file (changes from recent commits).

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

Re-trigger cubic

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview re-verification — verify-socials redesign · preview chat-3zrgpw6k4-recoup.vercel.app (sha-verified against head 80d8734a), Chrome DevTools MCP, same account cd59ed42 (Del Water Gap + a manually-added QA Roster Artist). Supersedes the earlier 10/10 run, which covered the pre-redesign Correct/Wrong UI.

The step-2 UI was reworked to accept-by-default (65cd47a1, net −428 LOC — dropped the Correct/Wrong verdicts, resolution/gating, and "no socials" button), then a follow-up (80d8734a) made Add-a-profile available on every artist.

# Check Actual Result
1 No per-social verdicts (accept by default) No Correct/Wrong; matched social shown as a read row (platform · handle · followers)
2 Copy reflects accept-by-default "These are the profiles we matched. Fix any that point at the wrong account…"
3 Per-social Edit (fix a wrong link in place) "Edit Spotify link" pencil reveals an inline paste-link form (fix save verified — replaces + closes)
4 Add a profile on a matched artist (was missing) Del Water Gap (1 Spotify social) now shows "Add a profile" → opens an add form
5 Add-a-profile on an unmatched artist + soft nudge QA Roster Artist: "No profiles were auto-matched… Add one, or continue." + "Add a profile"
6 No "This artist has no socials" button Removed
7 Continue always enabled (empty = implicit none) "Looks good — continue" enabled with nothing touched; proceeded to done with an artist still un-added
8 Completes to a done panel "Roster verified"
9 Console clean zero errors

9/9. Net result vs. the shipped version: strictly fewer controls (per-artist Edit + Add, one always-on Continue), accept-by-default.

Step 2 — accept-by-default, Edit + Add on the matched artist, Add + nudge on the unmatched one:
step2 final

"Add a profile" now works on a matched artist too (Del Water Gap):
add on matched

Done:
done

Two follow-ups tracked on #1867 (not in this PR): (1) Remove/delete a social — needs a new api DELETE endpoint (docs → api pair); the SocialRow is ready to wire a Remove ×. (2) Spotify search-as-you-type for Add/Edit (reuse /api/spotify/search for marketing parity) — non-Spotify platforms have no search endpoint anywhere yet, so they stay paste/handle.

@sweetmantech
sweetmantech merged commit 5538b27 into test Jul 21, 2026
4 checks passed
sweetmantech added a commit that referenced this pull request Jul 22, 2026
…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>
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