Repository navigation
feat(leads): per-report share cards + "watch this URL" recurring capture - #117
Merged
Merged
Conversation
Two milestones of the lead engine (docs/lead-engine-prd.md). M1 — shareable scorecard. Every /r/<token> link previously shared the one static /banner.png, so a report for acme.com and one for example.org produced byte-identical previews in Slack, X, and LinkedIn. Reports now generate their own card naming the scanned site and its score. Two wiring gotchas, both documented in the PRD: a page declaring openGraph.images overrides the generated opengraph-image (the banner had to be removed), and a page declaring an openGraph block but no twitter block inherits the root layout's twitter.images — which also outranks the file convention, so /slop briefly shipped the new card everywhere and the old banner on X alone. The card model is pure and tested rather than baked into the renderer, because the branching is easy to get backwards: slop and AEO scores run in OPPOSITE directions, and audits.score is not the number a slop report shows (that lives in summary.slopScore). The page title is now derived from the same model, so the text preview and the image can't disagree. Also adds the /slop landing page and its own static card, with the "observable defects, never AI-written" positioning stated as a feature. M2 — watch this URL. The report stays fully public; what costs an email is the ongoing relationship. A watch is double opt-in by construction (the row is inert until verified_at is set), capped per address and per IP, honours the global marketing suppression list, and carries an RFC 8058 one-click stop on every send. Re-scans are restricted to the free self-hosted engines, so a watch can never become recurring LLM spend on an address we never charged. The cron ticks every 15 minutes and does two things in order: deliver re-scans that finished on an earlier tick, then enqueue those now due. Emails only send when the score really moved (2+ points — smaller deltas are run-to-run jitter, and mailing about them trains people to ignore us), and the copy accounts for the inverted slop dial so "60 → 40" reads as the good news it is. Resulting audits are tagged to the existing Prospects org. The migration is NOT yet applied. Apply it after this deploys — it schedules a pg_cron job that POSTs to a route that won't exist until then. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ships M1 and M2 of the lead engine (
docs/lead-engine-prd.md, included).M1 — Shareable scorecard
Every
/r/<token>link previously pointed at the same static/banner.png, so a report for acme.com and one for example.org produced byte-identical previews in Slack, X, and LinkedIn. Reports now generate their own card naming the scanned site and its score. This retroactively upgrades every report ever generated.lib/audit/share-card.ts— the card model, kept pure and tested because the branching is easy to get backwards: slop and AEO scores run in opposite directions (0 is pristine for slop), andaudits.scoreis not the number a slop report displays — that'ssummary.slopScore. On a live prod scan those are 45 and 40 on the same row, so the old title would have printed "45/100" beside a card reading 40. The title now derives from the same model.app/r/[token]/opengraph-image.tsx— the renderer./sloplanding page + its own static card, with the "observable defects, never AI-written" line stated as a feature.Two wiring gotchas, documented in the PRD for whoever adds the next card:
openGraph.imagesoverrides the generatedopengraph-image— the static banner had to be removed for the card to take effect.openGraphblock but notwitterblock inherits the root layout'stwitter.images, which also outranks the file convention./slopinitially shipped the new card everywhere and the old banner on X alone.M2 — Watch this URL
The report stays fully public — gating it would kill the sharing loop M1 exists to create. What costs an email is the ongoing relationship, and the ask is self-qualifying: whoever wants a site re-scanned weekly is the person responsible for it.
verified_atis set. Capped per address and per IP, honours the global marketing suppression list, RFC 8058 one-click stop on every send./recent's outreach form can work them.20260726120000_scan_watches.sqlis not applied. Apply it after this deploys — it schedules a pg_cron job POSTing to/api/cron/scan-watchesevery 15 minutes, which 404s until the code is live. It also widensaudits_triggered_by_checkto allow'watch'(checked against prod: 4,017 rows / 29 MB, so validation is milliseconds).Verification
npm run typecheckclean; 534 tests pass (39 new acrosstests/share-card.test.tsandtests/watches.test.ts);npm run buildexits 0.og:imageandtwitter:image.Note
Also includes
docs/reshare-network-prd.md, which was already untracked in the working tree.🤖 Generated with Claude Code