/api/image/generate - OG Image - #4
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds openGraph metadata to the Next.js layout configuration, including title, description, and an image asset reference. Augments the exported metadata object without modifying runtime behavior or component structure. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes
Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
* feat(apify): notify only on genuinely new posts — diff against stored posts first The Instagram scrape alert fired whenever a scrape returned posts, with no comparison against posts already stored — every scrape re-announced the profile's recent feed as new (observed: 6 of 7 alerts in one day announced posts up to 10 days old). New reusable filterNewPostUrls diffs candidate URLs against posts BEFORE upsert; the alert is gated on a non-empty result. Persistence unchanged (recoupable/chat#1855, PR #3 of 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): one consolidated new-posts digest per scrape batch A roster scrape starts one Apify run per platform, each completing independently — extending per-platform alerts would mean 4+ emails per scrape. This registers every run under a batch_id at scrape start (apify_scraper_runs, columns from recoupable/database#41), records each webhook completion with its genuinely-new post URLs, and when the batch's last run completes sends ONE digest (per-platform sections, BCC-only) via the new digest module. Platforms with nothing new are omitted; a batch with nothing new sends nothing. Instagram's solo alert is suppressed for batch runs; legacy/non-batch runs keep today's behavior (recoupable/chat#1855, PR #4 of 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(supabase): align apify_scraper_runs helpers with naming convention Review feedback on #760: completeApifyScraperRun -> updateApifyScraperRun, insertApifyScraperRuns -> upsertApifyScraperRuns (it upserts on run_id), selectApifyScraperRunsByBatch -> selectApifyScraperRuns with an optional {batchId} filter object, matching the select* convention. Also repoints the stale types.ts reference from database#41 to #47. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(supabase): use generated types for apify_scraper_runs + zod-parse the JSONB column Regenerates database.types.ts (table landed in database#47), deletes the hand-rolled lib/supabase/apify_scraper_runs/types.ts shim, and drops every 'as never' cast — the helpers now use Tables/TablesInsert like every sibling lib. new_post_urls is the one column codegen can't narrow past Json, so a zod boundary (parseNewPostUrls) validates it at read time; malformed JSONB degrades to 'no new posts' instead of crashing the digest assembler. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(apify): move parseNewPostUrls out of lib/supabase into lib/apify/digest Review feedback: lib/supabase is for direct queries only; this is a pure JSONB parser consumed by the digest assembler. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… post, no LLM body (#761) * feat(apify): notify only on genuinely new posts — diff against stored posts first The Instagram scrape alert fired whenever a scrape returned posts, with no comparison against posts already stored — every scrape re-announced the profile's recent feed as new (observed: 6 of 7 alerts in one day announced posts up to 10 days old). New reusable filterNewPostUrls diffs candidate URLs against posts BEFORE upsert; the alert is gated on a non-empty result. Persistence unchanged (recoupable/chat#1855, PR #3 of 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): one consolidated new-posts digest per scrape batch A roster scrape starts one Apify run per platform, each completing independently — extending per-platform alerts would mean 4+ emails per scrape. This registers every run under a batch_id at scrape start (apify_scraper_runs, columns from recoupable/database#41), records each webhook completion with its genuinely-new post URLs, and when the batch's last run completes sends ONE digest (per-platform sections, BCC-only) via the new digest module. Platforms with nothing new are omitted; a batch with nothing new sends nothing. Instagram's solo alert is suppressed for batch runs; legacy/non-batch runs keep today's behavior (recoupable/chat#1855, PR #4 of 6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): deterministic digest template with direct links to each new post Replaces the per-send LLM email body (nondeterministic branding, vendor jargon reaching customers, no reliable post links) with a shared deterministic renderer: stable subject/branding, one section per platform, a direct link to every genuinely-new post, chat CTA secondary. Used by both the batch digest and the legacy solo Instagram alert, which now also requires new-post URLs and is BCC-only (recoupable/chat#1855, PR #5 of 6; supersedes the interim body from PR #4 and, for this file, the standalone BCC fix in api#758 — same invariant, tests included). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(apify): guard sendScrapeDigestEmail's renderer wiring + BCC invariant The branch imported renderScrapeDigestHtml in sendScrapeDigestEmail but never called it — the digest still rendered the old inline body (found during the main-sync conflict resolution; wired in that merge commit). This test fails against the unwired version: it asserts the send payload is byte-identical to the renderer's output, plus the BCC-only invariant and the empty-input no-send. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): house-style digest template with post media, captions, and dates The v1 deterministic template regressed visual quality vs the old LLM emails (bare h3/ul list). This upgrades the renderer to the DESIGN.md house style — achromatic chrome, card-per-post with 72px thumbnail, escaped caption excerpt, date, and a direct link; black CTA button — while staying deterministic and email-safe (tables + inline styles). Media plumbing: extractPostsFromDatasetItems maps platform dataset items (IG latestPosts, TikTok items) to {url, caption, thumbnailUrl, timestamp}, limited to the genuinely-new URLs. The solo alert enriches from the dataset already in memory; the digest enriches via getRunDigestSection, which re-reads each run's dataset from Apify (source of truth) and degrades to URL-only links on any failure — enrichment never blocks a send. Captions are HTML-escaped so scraped content can't inject markup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: prettier pass on the digest template files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): digest addressed by artist name + per-post engagement stats + fixed chat CTA Three feedback items on the template: - The digest header/subject said 'Your artist' — the assembler never passed a name. getRunDigestSection now also extracts the profile display name from the dataset (IG fullName, TikTok authorMeta.nickName) and maybeSendScrapeDigest addresses the email with the first platform's name. - Post cards now carry compact engagement stats (12.3K likes · 678 comments · 1.2M views · shares) mapped from platform counts (IG likesCount/commentsCount/videoViewCount, TikTok diggCount/commentCount/ playCount/shareCount); omitted entirely when the scraper returns none. - The CTA now always points at https://chat.recoupable.dev — previously it derived from the deployment base URL, which on previews is the API deployment itself. Funnel-tracking landing page tracked as follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): Recoup logo in the email header + artist-named roster footer Header is now a two-cell row with the brand icon top-right (hosted PNG — email clients don't render SVG) linking to recoupable.com. Footer names the artist: 'because {artist} is in your roster on Recoup', falling back to 'this artist' when no profile name resolved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(apify): strict SRP/DRY pass on the digest renderer + delete orphaned pre-rename supabase files Review feedback: - Every helper gets its own lib file: escapeHtml (lib/emails — repo had none), formatUtcDateLabel, truncateText, formatCompactCount, formatPostStats, getPlatformLabel, extractInstagramPosts, extractTiktokPosts, buildPostStats, asRecord/asStringOrNull/ asNumberOrNull coercers. renderScrapeDigestHtml and extractPostsFromDatasetItems now contain only their eponymous functions. - CHAT_APP_URL / WEBSITE_URL / RECOUP_LOGO_URL move to lib/const.ts as shared constants (no existing shared home found for platform labels — getPlatformLabel is the new one). - Deletes 4 orphaned pre-rename files under lib/supabase/apify_scraper_runs (completeApifyScraperRun, insertApifyScraperRuns, selectApifyScraperRunsByBatch, types): this branch predates #760's renames, so the merge of main added the renamed set alongside the branch's inherited old set — both landed with zero conflicts and zero references to the old files. Also confirms no legacy LLM email generation remains in lib/apify (generateText usage gone with the template rewrite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(apify): X/Twitter digest extractor + LinkedIn platform label Review feedback: Twitter's handler already feeds the digest (filterNewPostUrls -> newPostUrls) but had no extractor, so its sections degraded to URL-only links. extractTwitterPosts maps apidojo tweet items (fullText, extendedEntities media, createdAt via toIsoDate, like/reply/view/retweet counts), registered under both 'twitter' and 'x'. Artist-name extraction gains the same aliases (author.name ?? userName). LinkedIn added to platform labels. YouTube and LinkedIn deliberately have no extractors: their results handlers persist only the social row (no posts, no newPostUrls), so they never contribute digest sections today — extractors would be dead code until post persistence ships for them (chat#1833 territory). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(apify): only persist and report the artist's own tweets — no retweets or replies Feedback from a real-account digest test: an all-retweet X section reported hundreds of likes the artist never earned (retweet items carry the ORIGINAL author's stats). isOriginalTweet keeps originals and quote tweets (the artist's own words and metrics), drops retweets and replies, applied at the persistence layer in handleTwitterProfileScraperResults so both stored posts and digest sections stay accurate. Items without flags pass (defensive default on schema drift). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(apify): fetch 10 X timeline items by default — depth 1 rarely survives the retweet filter Real-account evidence (2026-07-09): the user's timeline had 7 retweets above their 2 originals and a quote tweet; a depth-1 (or 3) fetch returned only retweets, which isOriginalTweet now drops — so no authored post could ever reach the digest. Fetch deeper, filter after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.