Repository navigation
feat(web): ungated guide reader with a scored CTEM readiness checklist - #163
Merged
Merged
Conversation
Adds /read/ctem-guide — an on-site reader for the CTEM whitepaper, modelled on the vendor "content viewer" pattern used for lead capture. The existing /get-whitepaper page is a gate: form first, PDF download, visitor gone. Nothing about it is indexable and nothing about it tells us whether the lead ever read the thing. The reader inverts that — the guide is readable immediately, and the ask comes after engagement. What lands: - /read/ctem-guide, statically prerendered with Article + BreadcrumbList JSON-LD. The gated page is client-only and exports no metadata at all, so 4,000 words of content were invisible to search until now. - A sticky reading-progress indicator, a section rail with scroll-spy, and a 27-control CTEM readiness checklist that scores the reader live and names the controls they did not tick. - The checklist is the conversion mechanic: the gap list is worth an email in a way an unread PDF is not. Leads carry checklist_score, band and answers, so sales sees the score, not just an address. - Engagement beacons to /api/reader-events (scroll depth, engaged seconds, furthest section) via sendBeacon. Progress ratchets upward in SQL because beacons have no ordering guarantee and a stale 25% must not overwrite 80%. - Scores are recomputed server-side from the raw answers; a client-supplied score would be the one number sales cares about and the easiest to forge. The checklist ships in the PDF too, as a print-ready Appendix A (14 -> 17 pages). docs/ctem-checklist.json is the single source: build-whitepaper.mjs renders it into the PDF and emits apps/web/src/content/ctem-guide.generated.ts for the reader, so the two cannot drift. Also fixes the build script's Chrome detection, which only ever looked in /usr/bin and /snap/bin and so silently skipped PDF rendering on any machine that had Chrome via Puppeteer. It now honours $CHROME_PATH and falls back to the Puppeteer cache, newest version first. Nav, footer and landing-page CTAs point at the reader; /get-whitepaper keeps the PDF download and links across to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NEmhXFPa3HEJ3XeBWnC96Y
| {GUIDE_SECTIONS.map((section) => ( | ||
| <section key={section.id} id={section.id} className="scroll-mt-28"> | ||
| {section.id !== "top" && <h2>{section.title}</h2>} | ||
| <div dangerouslySetInnerHTML={{ __html: section.html }} /> |
ThreatCrush Security Scan12 finding(s) HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5
Snippets are redacted; ThreatCrush never prints matched credential material. |
| const [tocOpen, setTocOpen] = useState(false); | ||
|
|
||
| const sessionIdRef = useRef(""); | ||
| const [sessionId, setSessionId] = useState(""); |
| const beacon = useCallback((completed = false) => { | ||
| if (!sessionIdRef.current) return; | ||
| sendProgress({ | ||
| session_id: sessionIdRef.current, |
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.
What
An on-site reader for the CTEM guide at
/read/ctem-guide, built on the vendor "content viewer" pattern (Huntress et al.) — the guide is readable immediately, and the lead ask comes after engagement rather than in front of it.Why
/get-whitepaperis a gate: form first, PDF download, visitor gone. Two problems with that:"use client"at the top level with nolayout.tsx, so it exports no metadata at all. Four thousand words of genuinely good content have never been indexable.What's in it
/read/ctem-guide— statically prerendered (○in the build output), withArticle+BreadcrumbListJSON-LD, canonical, and OG tags.checklist_score,checklist_bandandchecklist_answers, so sales sees the score rather than just an address./api/reader-events— scroll depth, engaged seconds, furthest section — viasendBeacon.Notes for review
sendBeacongives no ordering guarantee and a backgrounded tab flushes late, so a plain upsert would let a stale 25% overwrite a live 80%.record_reader_progressusesgreatest()and a stickycompleted.anonandauthenticatedby name, not justPUBLIC— revoking from PUBLIC does not remove Supabase's explicit role grants, which would leave asecurity definerfunction callable at/rest/v1/rpc/<name>.docs/ctem-checklist.jsonis the single source:build-whitepaper.mjsrenders it into the PDF and emitsapps/web/src/content/ctem-guide.generated.tsfor the reader, so the two cannot drift./usr/binand/snap/bin, so it silently skipped PDF rendering on any machine with Chrome via Puppeteer. It now honours$CHROME_PATHand falls back to the Puppeteer cache, newest version first.No CI applies migrations here.
supabase/migrations/20260829120000_reader_sessions.sqlmust be applied to prod (odhaoehucfyrqhanthyq) or/api/reader-eventsand/api/checklistwill 500 on a missing RPC. Not applied yet — needs a go-ahead.Testing
npx tsc --noEmitclean;next buildclean,/read/ctem-guideprerenders static./api/checklistcovering score recomputation, junk-id rejection, deduplication, anonymous vs lead paths, and that every score 0–100 maps to exactly one band.expoworkflow token,usage/topupmessage text), untouched here.🤖 Generated with Claude Code
https://claude.ai/code/session_01NEmhXFPa3HEJ3XeBWnC96Y