fix(socials): canonicalize twitter.com → x.com in normalizeProfileUrl
Summary
Canonicalizes twitter.com/* → x.com/* inside normalizeProfileUrl so every reader/writer agrees on one socials.profile_url upsert key. Fixes the X scrape gap where results persisted to orphaned x.com/* twin rows while artists stayed connected to stale twitter.com/* rows.
Tracking issue: recoupable/chat#1851 (PR #1 of 2 required + 1 optional). Canonical-domain decision (x.com — official brand, matches the Apify actor's echo) recorded on the issue 2026-07-06.
What changed
lib/socials/normalizeProfileUrl.ts: after stripping protocol/www./trailing slash, rewrite a leadingtwitter.comhost tox.com(anchored —nottwitter.comand path occurrences untouched; case-insensitive host match; preserves handle casing).lib/socials/__tests__/normalizeProfileUrl.test.ts: 5 new canonicalization cases (equal keys for both spellings, x.com passthrough, no-false-positive domains, bare domain); 2 existing expectations updated to the new contract.
Merge sequencing
No docs PR — no request/response contract change (same reasoning as docs#262 closed unmerged on chat#1840). This PR leads; the database dedupe migration (chat#1851 PR #2) follows it, since the migration implements the same canonical-domain decision. Independent of the optional tweetless-fallback PR.
Tests
- RED→GREEN: new cases failed against the old implementation (5 failed | 9 passed), pass after (14 passed).
- Full domain sweep:
vitest run lib/socials lib/apify lib/artist lib/artists→ 53 files, 263 tests passed. tsc --noEmit: 200 pre-existing errors on cleanorigin/test(all inlib/triggertests), 200 after — no new errors. eslint clean on changed files.
Done-when (from chat#1851)
- Unit test
normalizeProfileUrl("https://x.com/Foo") === normalizeProfileUrl("https://twitter.com/Foo") - Live X scrape for an artist connected to a
twitter.com/*row updates that row (preview verification pending — note: full effect on existing rows also needs the PR #2 migration, since current connected rows keep theirtwitter.comkeys until migrated)
🤖 Generated with Claude Code
Summary by cubic
Canonicalizes twitter.com/* to x.com/* during profile URL normalization so all readers/writers use the same upsert key. Fixes X scrape write-backs landing on orphaned x.com rows while artists stayed linked to twitter.com (chat#1851).
- Bug Fixes
- After stripping protocol/www/trailing slash, rewrite leading twitter.com host to x.com (anchored, case-insensitive, preserves handle casing).
- Tests cover canonicalization, equal keys for both spellings, x.com passthrough, no false positives, and bare domain; updated prior expectations accordingly.
Summary by CodeRabbit
-
Bug Fixes
- Profile URLs are now normalized more consistently, including converting Twitter/X profile links to the preferred domain format.
- Existing cleanup still removes protocol,
www., and trailing slashes so links are stored in a uniform way.
-
Documentation
- Expanded guidance for how profile URL normalization handles domain aliases.