Skip to content

feat(onboarding): remove a social during verify-socials (chat#1889 row 9) - #1894

Merged
sweetmantech merged 2 commits into
mainfrom
feat/verify-socials-delete
Jul 29, 2026
Merged

feat(onboarding): remove a social during verify-socials (chat#1889 row 9)#1894
sweetmantech merged 2 commits into
mainfrom
feat/verify-socials-delete

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Matrix row 9 in chat#1889 — flagged as an onboarding blocker. Independent of the convergence chain.

Why

useSocialFix exposed only fixSocial (add or replace). There was no delete. So a user who added the wrong profile — or who had a bad auto-match they wanted gone rather than corrected — was stuck with a list they could not fix, mid-setup. Combined with a no-matches state whose copy read like an error, the step felt like a dead end.

What changed

  • lib/artists/deleteArtistSocial.ts — wraps the already existing DELETE /api/artists/{id}/socials/{socialId} endpoint. No api or docs change needed.
  • hooks/onboarding/useSocialRemove.ts — mutate + refresh the roster, mirroring useSocialFix's shape (own hook rather than a second verb on useSocialFix, per one-export-per-file).
  • SocialRow gains a Remove affordance beside Edit, with a per-platform aria-label.
  • ArtistSocialsCard / VerifySocialsStep thread it through; rows disable while a removal is in flight.
  • Copy: "No profiles were auto-matched … Add one, or continue""Adding one is optional, and you can continue without it", so an empty state doesn't read as a failure.

Verification

TDD, red → green:

```

RED — no remove affordance exists

× offers a remove affordance for a social the user does not want
Tests 1 failed | 1 passed (2)

GREEN

pnpm exec vitest run components/Onboarding
Test Files 1 passed (1)
Tests 2 passed (2)
```

The second test guards that Edit survives alongside Remove, so this can't silently replace the fix path.

pnpm exec tsc --noEmit clean for the touched files. Preview click-through pending (needs an authed roster with matched socials).

Tracked in chat#1889 (matrix row 9).


Summary by cubic

Adds a Remove action to verify-socials so users can delete a wrongly matched profile and continue onboarding. Addresses chat#1889 (row 9) by unblocking the step.

  • New Features

    • deleteArtistSocial wraps DELETE /api/artists/{id}/socials/{socialId}.
    • useSocialRemove deletes a social and refreshes the roster.
    • SocialRow adds Remove beside Edit; wired through ArtistSocialsCard and VerifySocialsStep; rows disable during removal.
    • Empty-state copy clarifies adding is optional.
  • Refactors

    • Extracted SocialRowActions into its own file; SocialRow composes it for Edit and Remove.

Written for commit ceda34c. Summary will update on new commits.

Review in cubic

@cursor

cursor Bot commented Jul 27, 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 27, 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 6:39pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 63992c7a-88c5-447f-a7e0-74609312f4f9

📥 Commits

Reviewing files that changed from the base of the PR and between 5c2b93b and ceda34c.

⛔ Files ignored due to path filters (1)
  • components/Onboarding/__tests__/SocialRow.test.tsx is excluded by !**/*.test.* and included by components/**
📒 Files selected for processing (6)
  • components/Onboarding/ArtistSocialsCard.tsx
  • components/Onboarding/SocialRow.tsx
  • components/Onboarding/SocialRowActions.tsx
  • components/Onboarding/VerifySocialsStep.tsx
  • hooks/onboarding/useSocialRemove.ts
  • lib/artists/deleteArtistSocial.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/verify-socials-delete

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread components/Onboarding/SocialRow.tsx Outdated
className="text-muted-foreground"
aria-label={`Remove ${platform} link`}
disabled={isSubmitting}
onClick={() => void onRemove(social.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.

P1 Badge Preserve onboarding completion after removing the last social

When this button removes an artist's final profile, useSocialRemove refreshes that artist to account_socials: [], but getOnboardingCheckpoints defines the socials checkpoint as complete only when every artist has at least one social. Consequently, the targeted user who removes their only incorrect auto-match can click “continue” but will be returned to an incomplete socials step on the next home landing and cannot complete onboarding without re-adding a profile or skipping. Persist an explicit verified-with-none state or change the checkpoint semantics before allowing the final profile to be removed.

Useful? React with 👍 / 👎.

Comment on lines +38 to +40
isFixing={
fixingArtistId === artist.account_id || removingSocialId !== null
}

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 Block removals while another social mutation is pending

While a fix is pending for artist A, cards for every other artist still receive isFixing={false}, so their new Remove buttons remain enabled. A removal can therefore overlap useSocialFix; both hooks then call the same getArtists/fetchQuery, and if the second mutation completes while the first roster refetch is in flight, React Query can share that in-flight request and cache a snapshot taken before the second mutation. The removed row can reappear until a later refetch, and retrying it may produce a not-found error. Treat any non-null fixingArtistId as globally busy, as is already done for removals, or otherwise serialize/invalidate these mutations.

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.

4 issues found across 6 files

Confidence score: 3/5

  • In components/Onboarding/SocialRow.tsx, removing an artist’s last social can flip the onboarding checkpoint to incomplete while navigation can still advance, creating inconsistent step state and a likely user-flow regression — block last-social removal or immediately enforce/add a replacement before allowing progression.
  • In components/Onboarding/VerifySocialsStep.tsx, the removingSocialId !== null condition disables all ArtistSocialsCard instances during one removal, so unrelated artists are temporarily blocked from fixes/removals and the step can feel frozen — scope disablement to the affected artist/card only.
  • In hooks/onboarding/useSocialRemove.ts, a roster refresh failure after a successful DELETE is surfaced as a failed removal, leaving stale UI and misleading error state — treat delete success separately and update local roster state (with retry/fallback) even if the refresh call fails.
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/useSocialRemove.ts">

<violation number="1" location="hooks/onboarding/useSocialRemove.ts:34">
P2: A post-delete roster refresh failure is reported as a failed removal even though the DELETE already succeeded. The stale row remains visible and `false` leaves the UI in its pre-delete state; update the local roster optimistically (or handle refresh failure separately) once `deleteArtistSocial` resolves.</violation>
</file>

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

<violation number="1" location="components/Onboarding/VerifySocialsStep.tsx:38">
P2: When a removal is in progress, `removingSocialId !== null` disables all `ArtistSocialsCard` components, not just the card for the artist whose social is being removed. During a fix, only the affected artist's card is disabled via `fixingArtistId === artist.account_id`. This inconsistency means removing from artist A also disables artist B's add and fix actions. Track the removing artist ID in `useSocialRemove` alongside (or instead of) the social ID so the disable can be per-artist like the fix path.</violation>

<violation number="2" location="components/Onboarding/VerifySocialsStep.tsx:39">
P2: The disabling logic is asymmetric: a pending removal globally disables all cards (`removingSocialId !== null`), but a pending fix only disables the card for the artist being fixed. This means Remove buttons on *other* artists remain clickable while a fix is in flight, and both mutations call `getArtists()` on completion — a concurrent refetch can cache stale data and briefly resurrect a removed row. Treat any non-null `fixingArtistId` as a global busy signal as well (i.e., `fixingArtistId !== null || removingSocialId !== null`) to serialize the mutations.</violation>
</file>

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

<violation number="1" location="components/Onboarding/SocialRow.tsx:82">
P1: When this Remove button deletes the artist's only remaining social, the onboarding checkpoint (which gates on every artist having at least one social) will mark the socials step incomplete. The user can click "continue" but will be looped back on the next landing. Either guard this removal when it would leave `account_socials` empty (e.g., confirm or skip), persist an explicit "verified-with-none" state, or update the checkpoint semantics to treat an empty-after-removal list as complete.</violation>
</file>

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

Re-trigger cubic

Comment thread components/Onboarding/SocialRow.tsx Outdated
className="text-muted-foreground"
aria-label={`Remove ${platform} link`}
disabled={isSubmitting}
onClick={() => void onRemove(social.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.

P1: When this Remove button deletes the artist's only remaining social, the onboarding checkpoint (which gates on every artist having at least one social) will mark the socials step incomplete. The user can click "continue" but will be looped back on the next landing. Either guard this removal when it would leave account_socials empty (e.g., confirm or skip), persist an explicit "verified-with-none" state, or update the checkpoint semantics to treat an empty-after-removal list as complete.

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

<comment>When this Remove button deletes the artist's only remaining social, the onboarding checkpoint (which gates on every artist having at least one social) will mark the socials step incomplete. The user can click "continue" but will be looped back on the next landing. Either guard this removal when it would leave `account_socials` empty (e.g., confirm or skip), persist an explicit "verified-with-none" state, or update the checkpoint semantics to treat an empty-after-removal list as complete.</comment>

<file context>
@@ -51,17 +60,30 @@ const SocialRow = ({ social, isSubmitting, onFix }: SocialRowProps) => {
+            className="text-muted-foreground"
+            aria-label={`Remove ${platform} link`}
+            disabled={isSubmitting}
+            onClick={() => void onRemove(social.id)}
+          >
+            <Trash2 className="size-4" />
</file context>

throw new Error("Please sign in to remove a social");
}
await deleteArtistSocial(accessToken, artist.account_id, socialId);
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 post-delete roster refresh failure is reported as a failed removal even though the DELETE already succeeded. The stale row remains visible and false leaves the UI in its pre-delete state; update the local roster optimistically (or handle refresh failure separately) once deleteArtistSocial resolves.

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

<comment>A post-delete roster refresh failure is reported as a failed removal even though the DELETE already succeeded. The stale row remains visible and `false` leaves the UI in its pre-delete state; update the local roster optimistically (or handle refresh failure separately) once `deleteArtistSocial` resolves.</comment>

<file context>
@@ -0,0 +1,47 @@
+        throw new Error("Please sign in to remove a social");
+      }
+      await deleteArtistSocial(accessToken, artist.account_id, socialId);
+      await getArtists();
+      return true;
+    } catch (error) {
</file context>

key={artist.account_id}
artist={artist}
isFixing={fixingArtistId === artist.account_id}
isFixing={

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 a removal is in progress, removingSocialId !== null disables all ArtistSocialsCard components, not just the card for the artist whose social is being removed. During a fix, only the affected artist's card is disabled via fixingArtistId === artist.account_id. This inconsistency means removing from artist A also disables artist B's add and fix actions. Track the removing artist ID in useSocialRemove alongside (or instead of) the social ID so the disable can be per-artist like the fix path.

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

<comment>When a removal is in progress, `removingSocialId !== null` disables all `ArtistSocialsCard` components, not just the card for the artist whose social is being removed. During a fix, only the affected artist's card is disabled via `fixingArtistId === artist.account_id`. This inconsistency means removing from artist A also disables artist B's add and fix actions. Track the removing artist ID in `useSocialRemove` alongside (or instead of) the social ID so the disable can be per-artist like the fix path.</comment>

<file context>
@@ -33,8 +35,11 @@ const VerifySocialsStep = ({ onConfirmed }: { onConfirmed: () => void }) => {
             key={artist.account_id}
             artist={artist}
-            isFixing={fixingArtistId === artist.account_id}
+            isFixing={
+              fixingArtistId === artist.account_id || removingSocialId !== null
+            }
</file context>

artist={artist}
isFixing={fixingArtistId === artist.account_id}
isFixing={
fixingArtistId === artist.account_id || removingSocialId !== null

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 disabling logic is asymmetric: a pending removal globally disables all cards (removingSocialId !== null), but a pending fix only disables the card for the artist being fixed. This means Remove buttons on other artists remain clickable while a fix is in flight, and both mutations call getArtists() on completion — a concurrent refetch can cache stale data and briefly resurrect a removed row. Treat any non-null fixingArtistId as a global busy signal as well (i.e., fixingArtistId !== null || removingSocialId !== null) to serialize the mutations.

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

<comment>The disabling logic is asymmetric: a pending removal globally disables all cards (`removingSocialId !== null`), but a pending fix only disables the card for the artist being fixed. This means Remove buttons on *other* artists remain clickable while a fix is in flight, and both mutations call `getArtists()` on completion — a concurrent refetch can cache stale data and briefly resurrect a removed row. Treat any non-null `fixingArtistId` as a global busy signal as well (i.e., `fixingArtistId !== null || removingSocialId !== null`) to serialize the mutations.</comment>

<file context>
@@ -33,8 +35,11 @@ const VerifySocialsStep = ({ onConfirmed }: { onConfirmed: () => void }) => {
             artist={artist}
-            isFixing={fixingArtistId === artist.account_id}
+            isFixing={
+              fixingArtistId === artist.account_id || removingSocialId !== null
+            }
             onFix={(url) => fixSocial(artist, url)}
</file context>
Suggested change
fixingArtistId === artist.account_id || removingSocialId !== null
fixingArtistId !== null || removingSocialId !== null

chat#1889 matrix row 9 (onboarding blocker).

useSocialFix could only add or replace a profile. A user who added the wrong
social had no way to take it back and was left with a list they could not
correct, which reads as a dead end in the middle of setup.

- New deleteArtistSocial wraps the existing
  DELETE /api/artists/{id}/socials/{socialId} endpoint.
- New useSocialRemove hook mutates + refreshes the roster, mirroring
  useSocialFix.
- SocialRow gains a Remove affordance beside Edit; ArtistSocialsCard and
  VerifySocialsStep thread it through.
- The no-matches copy now states adding is optional and the step can be
  continued without it, instead of reading like an error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sweetmantech
sweetmantech force-pushed the feat/verify-socials-delete branch from d571997 to 0635e4b Compare July 29, 2026 18:29
Comment thread components/Onboarding/SocialRow.tsx Outdated
>
<Pencil className="size-4" />
</Button>
<div className="flex shrink-0 items-center">

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

OCP

  • actual: new component code added to existing component.
  • required: new component code for the buttons section is defined in a new file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in ceda34ca — extracted to components/Onboarding/SocialRowActions.tsx (one exported component; Edit + Remove cluster with the a11y labels intact). SocialRow composes it and dropped its now-unused Button/icon imports. 272 tests pass; tsc at the 8 baseline; eslint clean.

Review feedback on chat#1894: the edit/remove button cluster was net-new
component code inline in SocialRow. It now lives in SocialRowActions.tsx;
SocialRow composes it, so a future per-social action is a new prop wire,
not another inline block.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

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

Re-trigger cubic

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Preview verification — the dead end is gone, round-trip proven

Rebased onto latest main (was 8 behind) — one conflict, an import union with #1886's avatar work (avatar JSX verified intact). OCP feedback addressed in ceda34ca (SocialRowActions.tsx extracted, replied on the thread). Suite 272/279 green; tsc at the 8 baseline.

  • Preview: chat-p18c0lfnx-recoup.vercel.app (post-OCP sha ceda34ca), fresh account sweetman+july2920261327@…

The instrumented walk

Step Result
Verify-socials renders the matched row Edit + Remove buttons side by side (SocialRowActions)
Click Remove DELETE /api/artists/33dbcad4…/socials/904e54fc…200, row gone
Empty state after removal "Adding one is optional, and you can continue without it." — reads as a choice, not an error; Continue stays enabled
Round trip: re-add via the typeahead Row returns — as "@annie DiRusso · 68.8K followers"

remove affordance
optional empty state
re-added with real metadata

Incidental finding — narrows chat#1889 row 23's scope

The re-added row carries the real handle and follower count (@Annie DiRusso · 68.8K), while the auto-matched row showed the junk @artist · 0 followers. So the metadata bug lives only in the create-time social attach, not in the social-fix path this PR touches — row 23's fix should target updateArtistSocials/the attach, and this PR's remove→re-add is an interim user-level workaround for any junk row.

Ready to merge.

@sweetmantech
sweetmantech merged commit 9fcd2aa 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