================================================================================ CRITICAL GATE — TOKEN-FIRST (NON-NEGOTIABLE) ──────────────────────────────────────────────────────────────────────────────── Before any actions (scaffold, npm install, npm run dev/build, coding, analysis), the agent MUST: 1. Resolve token in strict order: a) MCP secret source b) runtime env: SE_RANKING_DATA_API_TOKEN c) project .env.local d) sibling/local env files (../*/.env.local, backups) 2. If token is not found: - Stop immediately. - Print this machine-readable blocking line first: Missing SE_RANKING_DATA_API_TOKEN in .env.local and MCP secret source returned no token. - Then print a human-friendly user message (required): SE_RANKING_DATA_API_TOKEN is missing. Please provide one of the following: 1) The SE_RANKING_DATA_API_TOKEN value, or 2) A path to a local env file where it already exists. How to get the token in SE Ranking: 1. Open SE Ranking app 2. In the left sidebar, go to API 3. Open Dashboard 4. Open API Keys 5. Copy Data API Key 6. Share the key here in chat (or share a path to a local env file where it already exists). - Do not run any other commands until user provides token value or env file path. 3. If token is found: - Write/update project .env.local. - Never print token values in chat, logs, terminal output, or UI. - Continue only after token gate passes. 4. Compliance checkpoints (mandatory): - First agent status message: Starting token gate check. - Second status message: Token gate status: PASS or Token gate status: BLOCKED. Any violation means the task is considered incomplete. Topic: AI Visibility Lead Magnet (ai-visibility-checker) ================================================================================ ## GOAL Build a lead magnet web app where a user enters their domain and sees how their brand stacks up against top competitors in AI search engines (ChatGPT, Perplexity, Gemini, AI Overview, AI Mode). Outputs a leaderboard with a CTA to hire the agency. -------------------------------------------------------------------------------- ## TECH STACK - Next.js 15 (App Router) - TypeScript - Tailwind CSS - SE Ranking Data API (https://api.seranking.com) ## TO RUN # Requires Node.js + npm in PATH (or project-local .tools/node/bin in PATH) # Configure Next.js basePath to `/ai-visibility-checker` # IMPORTANT: client API requests must use `${process.env.NEXT_PUBLIC_BASE_PATH}/api/...` (not `/api/...`) cd ~/ai-visibility-checker npm install npm run dev # → http://localhost:3000/ai-visibility-checker ## ENV SE_RANKING_DATA_API_TOKEN= # required for SE Ranking Data API LEAD_WEBHOOK_URL= # optional, for CRM/Zapier/Make forwarding ## TOKEN POLICY SINGLE SOURCE OF TRUTH (MANDATORY) All token resolution, blocking behavior, machine-readable error line, and user guidance are defined only in: - `CRITICAL GATE — TOKEN-FIRST (NON-NEGOTIABLE)` at the top of this instruction. Do not duplicate or redefine token-gate logic in other sections. ## LANGUAGE LOCALIZATION POLICY (MANDATORY) All user-facing guidance must be written in the user's language, while product names, API terms, endpoint names, environment variables, and other technical terms remain in English. Exception (must stay in Latin exactly as written): - SE Ranking app - API - Dashboard - API Keys - Data API Key For the token guidance block, translate every step/action into the user's language, while keeping the five terms above unchanged. Use the exact guidance content from `CRITICAL GATE — TOKEN-FIRST (NON-NEGOTIABLE)`; do not redefine it elsewhere. ## AGENT PERMISSIONS You are authorized to run `npm install`, `npm run build`, and `npm run dev` (including any required network/localhost access) without pausing for confirmation. ## Dependency & Install Policy (mandatory) Before coding or running: 1. Verify `node` and `npm` are available. 2. If missing, ask once for permission and auto-install (Homebrew or nvm on macOS; equivalent package manager on Linux/Windows). 3. Run `npm install`, then continue automatically. 4. Do not ask for confirmation before routine read-only checks or normal project commands when permissions allow. If a command requires admin password: 1. Tell user to open local terminal app (macOS Terminal/iTerm, Windows PowerShell/Terminal). 2. Explain password input is hidden during `sudo` (no symbols appear). 3. Provide one copy-paste command block. 4. Resume automatically after user confirms completion. ## Approval Popup Minimization (mandatory) To avoid noisy popups: 1. Do not ask optional "pre/final URL verification" questions. 2. Skip `curl` checks when dev server already started and app is reachable by normal run output. 3. Ask user only for privileged actions that genuinely need approval (installers, killing old port owner, writing outside workspace) or for missing secrets. ## Connectivity Check Policy (mandatory) To reduce unnecessary approval popups: 1. Do not run pre-check or final-check `curl` probes if dev server has already started successfully and user has not reported issues. 2. Return final app URL immediately after successful server start. 3. Run `curl`/HTTP verification only when: - user explicitly asks for verification, or - there are symptoms (404/500/runtime errors, unstable routing, failed handoff). 4. If verification is needed, prefer already-approved command prefixes and avoid duplicate confirmation prompts. ## Port 3000 Conflict Policy (mandatory) If dev server cannot bind to `localhost:3000` because port 3000 is already in use: 1. Ask once for permission to stop the old process using port 3000. 2. If approved, stop the old process and restart app on port 3000. 3. If not approved, continue on the fallback port and return that exact URL. 4. Prefer "allow and remember" for this action to reduce repeated prompts in future runs. ## Turbopack Fallback Policy (mandatory) If `next dev` or `next build` fails because of Turbopack sandbox/runtime issues (for example EPERM panic, cannot bind helper port, or Turbopack internal error): 1. Retry immediately with webpack fallback: - `next dev --webpack` - `next build --webpack` 2. Keep all other task requirements unchanged (token gate, basePath, final handoff contract). 3. Use fallback only when needed; keep default Next.js commands when Turbopack works. ## UI COPY HYGIENE (MANDATORY) 1. Do not render internal/local path hints in user-facing UI (examples: `/api/...`, localhost route details, basePath debug text). 2. Do not show implementation labels like "lead magnet" in visible product copy unless user explicitly requests it. 3. Keep technical path/config details in agent logs only, not inside landing/results UI. ## HYDRATION WARNING POLICY (MANDATORY) If Next.js dev mode shows hydration mismatch caused by browser extensions (for example `data-gr-ext-installed` / Grammarly attributes): 1. Treat as non-blocking unless user-facing behavior is broken. 2. Add `suppressHydrationWarning` at root HTML/body when needed to reduce noisy overlays. 3. Do not classify extension-injected attribute mismatch as app logic failure. ## COUNTRY DROPDOWN UI GUARDRAIL (MANDATORY) For the country selector, keep arrow alignment clean and consistent across browsers: 1. Use custom select arrow UI (`appearance-none`) instead of default browser arrow. 2. Render one custom chevron icon in a right-aligned absolute container. 3. Vertically center the icon with `top-1/2` + `-translate-y-1/2`. 4. Add extra right padding on the select input so text never overlaps the icon. 5. Keep icon non-interactive (`pointer-events-none`) so click area remains on the select. ## UI Action Layout Guardrails (mandatory) For completion/success states and CTA blocks: 1. Primary button and secondary action link must not overlap or appear as one inline row unless explicitly designed that way. 2. Primary CTA should be in its own centered block container (`flex justify-center` or equivalent). 3. Secondary action should render in a separate row (`block` + centered alignment). 4. Validate final UI on both desktop and mobile widths before handoff. 5. If overlap is detected, adjust layout classes first (spacing/display/container) before changing copy or removing actions. -------------------------------------------------------------------------------- ## PROJECT STRUCTURE ai-visibility-checker/ ├── app/ │ ├── layout.tsx # Root layout, Inter font, dark theme │ ├── page.tsx # Landing page — hero, form, how it works │ ├── globals.css # Tailwind base, dark color scheme │ ├── results/ │ │ ├── layout.tsx # Suspense wrapper for useSearchParams │ │ └── page.tsx # Results page — summary cards, leaderboard, CTA │ └── api/ │ ├── analyze/ │ │ └── route.ts # Main API route (orchestrates all SE Ranking calls) │ └── lead/ │ └── route.ts # Lead capture endpoint for CTA modal ├── components/ │ ├── DomainForm.tsx # Domain input + country picker + engine toggle buttons │ ├── Leaderboard.tsx # Tabbed leaderboard table (Overall + per-engine) │ └── CTASection.tsx # Adaptive CTA banner (red if losing, green if leading) ├── lib/ │ ├── seranking.ts # SE Ranking API client + all data logic │ └── countries.ts # 30-country dropdown list ├── .env.local # API token (gitignored) ├── .env.local.example └── package.json -------------------------------------------------------------------------------- ## USER FLOW 1. User lands on homepage 2. Enters domain (e.g. amazon.com) + picks country + selects AI engines 3. Clicks "Analyze My AI Visibility →" 4. Navigates to /results?domain=...&country=...&engines=... 5. Loading screen shows animated steps while analysis runs (~15-20 seconds) 6. Results page shows: - Summary cards: AI Rank, Share of Voice %, Brand Mentions, Link Citations - "Analyzed across: [ChatGPT] [Perplexity] [Gemini] ..." badges - Leaderboard table with Overall + per-engine tabs - CTA banner at bottom -------------------------------------------------------------------------------- ## API FLOW (app/api/analyze/route.ts) Step 1: GET /v1/domain/competitors → Find top 5 organic competitors for the entered domain Implementation note: - `/v1/domain/competitors` may return either: 1) raw array `[{ domain: ... }]` 2) object `{ items: [{ domain: ... }] }` - Parser must support both formats. - If no competitors are returned, show a clear UI message ("No competitors found for this domain/country") instead of silently showing only the primary domain. Step 2: GET /v1/ai-search/discover-brand (sequential, 1.5s delay between calls) → Resolve brand name for each of the 6 domains (user + 5 competitors) → Fallback: derive brand name from domain (pandadoc.com → PandaDoc) → API returns: { brands: ["BrandName", "Brand.com"] } → Takes first entry without a dot Step 3: GET /v1/ai-search/overview/by-engine/time-series (each domain x engine) → Independent metrics per domain per selected engine → 800ms delay between each call to avoid rate limiting → Extracts from: response.summary.brand_presence.current response.summary.link_presence.current response.summary.average_position.current Step 4: buildLeaderboard() → Sums metrics across selected engines per domain → Computes Share of Voice = brand_presence_i / total_brand_presence x 100 → Computes avg_position = weighted average across engines → Sorts by brand_presence descending → assigns ranks ## API PARAMETER CONTRACT + RESILIENCE (MANDATORY) Use endpoint-specific query params exactly as required by the active API contract. 1. `GET /v1/ai-search/overview/by-engine/time-series` - required params: `target`, `source`, `engine`, `country_code` - `source` is regional database country code (example: `us`), not engine name. - `engine` is LLM id; if UI uses underscore ids (`ai_overview`, `ai_mode`), map to API ids (`ai-overview`, `ai-mode`) before request. 2. `GET /v1/ai-search/discover-brand` - use `target=`, `source=`, `scope=domain`. - if discover-brand fails (429/500/invalid source), fallback to domain-derived brand name. 3. `GET /v1/domain/competitors` - use `domain=`, `source=`, `type=organic`. - parser must support both array and `{ items: [...] }` response shapes. - if competitors endpoint returns 4xx/5xx or empty set, do not fail entire report. 4. Page-level resilience rule: - `/api/analyze` must return a usable result even when competitor discovery fails. - fallback to single-domain report (primary domain only) and show a clear UI notice. - never hard-fail results page solely because competitors list is unavailable. -------------------------------------------------------------------------------- ## LEADERBOARD COLUMNS | Column | Description | |-----------------|-----------------------------------------------------------| | Rank | Medal or #N | | Brand / Domain | Brand name + domain, "You" badge on user's row | | Brand Mentions | Times brand appeared in AI answers (raw count) | | Link Citations | Times domain was linked in AI answers (raw count) | | Share of Voice | % of all group mentions (computed, shown for all brands) | | Avg. Position | Avg rank in AI responses (green <=3, yellow <=7, red >7) | Tabs: Overall | ChatGPT | Perplexity | Gemini | AI Overview | AI Mode - Overall: sums across all selected engines - Per-engine: independent metrics + per-engine SoV computed in component ## CREDIT USAGE TRANSPARENCY (MANDATORY) 1. On the landing/start screen, show a short credit-usage note before user runs analysis. 2. Default note copy: `Note: a full run usually consumes about 24,700 SE Ranking Data API credits.` 3. Treat this value as estimate for full run configuration (primary domain + 5 competitors + 5 engines). 4. If configuration differs (fewer competitors/engines), actual usage may be lower. ## API REQUEST DEDUPLICATION (MANDATORY) To avoid duplicate credit spend from repeated/parallel calls: 1. Do not allow duplicate in-flight `/api/analyze` requests for the same input tuple `(domain, country, engines)`. 2. Reuse one shared in-flight promise/result for identical concurrent requests. 3. Add short TTL cache for identical immediate re-requests (for example 60 seconds) to prevent accidental double charges. 4. This dedup guardrail is required in dev and production environments. ## LEADERBOARD + CTA CONSISTENCY (MANDATORY) 1. Leaderboard rank display: - Top 1 must show `🥇` - Top 2 must show `🥈` - Top 3 must show `🥉` - Rank 4+ must show `#N` - If rank is unavailable (0/null), show `-` (never show fake `Top 1`). 2. Bottom CTA message must match actual rank: - rank = 1 → leading message. - rank = 2..3 → close-to-leader message. - rank >= 4 → losing visibility message. - Do not show "You are leading" when rank is not #1. 3. Loading screen expectation: - Show a time estimate line under the progress bar. - Copy format: `This usually takes about 15-25 seconds.` 4. If competitors are missing, keep page functional with fallback notice, but do not infer leadership from missing data. -------------------------------------------------------------------------------- ## CTA SECTION (components/CTASection.tsx) - CTA button should open a lead-capture modal (not redirect). - Modal fields: Full name (required), Work email (required), Company, Website, Notes. - Submit to `POST /api/lead` (basePath-aware URL). - Show success/error state in modal. - Optional: forward lead payload to `LEAD_WEBHOOK_URL` if env var is set. -------------------------------------------------------------------------------- ## KEY BUGS FIXED DURING SESSION 1. 429 rate limiting on brand discovery → Changed from Promise.all (parallel) to sequential with 1.5s delay → Added fetchWithRetry with exponential backoff for 429/500 responses 2. API field name mismatch (target vs domain) → SE Ranking leaderboard returns `domain` field, not `target` → Fixed matching logic; use `is_primary_target` flag instead 3. Brand discovery response shape → API returns { brands: ["Name", "Name.com"] }, not { brand: "Name" } → Updated to read brands[0], preferring entry without a dot 4. Metrics were all zeros → Leaderboard endpoint only measures competitors in primary brand's prompt context → Switched to per-domain independent calls via by-engine time-series endpoint 5. Metrics response parsing → API returns { summary: { brand_presence: { current: N } } } → Was looking for flat { brand_presence: N } — fixed extractLatestFromSeries() 6. Share of Voice always empty → API's share_of_voice field is empty; now computed as: SoV = brand_presence / total_group_brand_presence x 100 → Also computed per-engine tab in Leaderboard component 7. Error handling for SE Ranking API responses → Show user-friendly messages by HTTP status: - 401: invalid/revoked token (`SE_RANKING_DATA_API_TOKEN`) — ask user to verify token. - 402: API key disabled for billing/subscription — clarify this is key/account status, not app request limits. - 403: token has no permission for endpoint. → Do not render raw HTML/JSON error bodies directly in UI. -------------------------------------------------------------------------------- ## KNOWN LIMITATIONS / TODO - CTA button links to #contact — replace with real agency URL - Agency branding is generic — add logo, colors, agency name - No caching — every report re-fetches live data (~15-20s) - discoverBrand still rate-limits occasionally (500 "too many requests") → Falls back to domain-derived name (Walmart, Ebay etc.) - Consider adding email capture before showing results (lead capture) - Consider adding a "Share report" / PDF export feature - Known issue: unbounded sibling env search may hang and slow token gate → Use bounded token lookup only (known sibling paths first, then maxdepth=2 `.env.local` scan, strict time budget). -------------------------------------------------------------------------------- ## SE RANKING API REFERENCE Base URL: https://api.seranking.com Auth header: Authorization: Token {DATA_API_TOKEN} Endpoints used: GET /v1/domain/competitors → find competitors GET /v1/ai-search/discover-brand → resolve brand name GET /v1/ai-search/overview/by-engine/time-series → per-domain AI metrics -------------------------------------------------------------------------------- ## RUNTIME COMPLETION CONTRACT (MANDATORY) Before final handoff, agent MUST complete all steps automatically: 1. Ensure dependencies are installed (`npm install`) if missing. 2. Ensure `SE_RANKING_DATA_API_TOKEN` is present in project `.env.local`. 3. Start dev server (`npm run dev`) and keep it running. 4. Verify app is reachable from Next.js startup output. 5. Return a single final app URL only after runtime is ready. Hard rule: - Do not stop at code generation. Task is incomplete until runtime is up and analysis path is unblocked by token setup. -------------------------------------------------------------------------------- ## FINAL RESPONSE FORMAT (MANDATORY) When successful, final response must contain: - exactly one working URL for the running app (do not duplicate it in multiple lines) - one short instruction in the user's language to open it in browser (for example: "Open in browser: http://localhost:3000/ai-visibility-checker") Do not include setup steps, long summaries, or extra instructions in successful handoff. Include extra text only when blocked by missing token after all automatic bootstrap attempts. -------------------------------------------------------------------------------- ## BASEPATH DUPLICATION PREVENTION (MANDATORY) To prevent 404 caused by duplicated route prefix (example: `/ai-visibility-checker/ai-visibility-checker/results?...`): 1. If Next.js `basePath` is configured (for example `/ai-visibility-checker`), do not manually prepend it in `router.push()` path navigation. 2. Use app-relative navigation paths (for example `/results?...`) so Next.js applies `basePath` automatically. 3. Keep client API calls basePath-aware with: `${process.env.NEXT_PUBLIC_BASE_PATH}/api/...` 4. Before final handoff, verify generated URLs do not contain duplicated segments like: `/ai-visibility-checker/ai-visibility-checker/...` 5. If duplicated basePath is detected, fix navigation path construction first (do not remove `basePath` from Next config unless explicitly requested). -------------------------------------------------------------------------------- ## BASEPATH GUARDRAIL (ENGLISH MIRROR) This is explicitly documented in the instruction: - do not duplicate `basePath` in `router.push`, - use `/${route}` for navigation (Next adds `basePath` automatically), - for client API calls use `${process.env.NEXT_PUBLIC_BASE_PATH}/api/...`, - verify before handoff that the URL does not contain `.../ai-visibility-checker/ai-visibility-checker/...`.