Skip to content

feat(geo): AI visibility tracking across AI engines - #657

Open
mezotv wants to merge 15 commits into
mainfrom
feat/geo-ai-visibility
Open

feat(geo): AI visibility tracking across AI engines#657
mezotv wants to merge 15 commits into
mainfrom
feat/geo-ai-visibility

Conversation

@mezotv

@mezotv mezotv commented Aug 5, 2026

Copy link
Copy Markdown
Member

Description

Give a short summary of what this PR does and why it's needed.

Screenshot/Recording (if applicable)

Attach a screenshot or recording of the change. This is optional, but can help reviewers understand the change. You can use Cap to record a video.

Checklist
  • I ran a self-review before opening this PR
  • I ran formatting/linting/type checks locally
  • I updated docs when behavior or setup changed
  • I only added comments where the logic is not obvious
  • I have used conventional commits for the PR title and commit messages
  • I did not use AI to write the code in this PR or have disclosed that I did

Summary by cubic

Adds GEO brand visibility tracking across AI engines with prompt scans, AI traffic ingest, journeys, and dashboards for share of voice, engine coverage, and trends. Integrates with the social analytics ClickHouse base and ships an ingest pipeline and Directions reports.

  • New pages & metrics: GEO Overview, Prompts, Competitors (page + modal), Directions (Instrument, Leaderboard, Cockpit, Report) with engine rates/trends, share of voice, prompt funnel, model/language usage, journeys, AI traffic log/pages.

  • Project scoping & settings: project switcher + context; settings for company, aliases, languages, competitors; onboarding from website.

  • Prompts: create prompts, run scans, and view per‑engine results; grounded (web) and raw engine checks with judging and excerpts; results preview.

  • Ingest & workflows: POST /api/geo/ingest with HMAC token auth, Effect‑based pipeline, visitor classification, and journey attribution; POST /api/workflows/geo-scan with QStash signature verification; docs and a Next proxy snippet for @usenotra/geo.

  • Data/API & UX: ORPC geo router, React hooks, schemas, and mappers; GEO nav category and command palette routes; improved skeletons/tables; new funnel chart and chart animation/formatters.

  • Dependencies: add @ai-sdk/anthropic, @ai-sdk/openai, @ai-sdk/perplexity, @usenotra/geo, sugar-high, color; update Next transpile list to include @usenotra/geo.

  • Refactors

    • Removed legacy chart code and unused files; expanded knip.json coverage; repaired analytics migration chain; dropped analytics backfill script.
    • Extracted conversation sequences into a stacked follow‑up to reduce scope.
  • Bug Fixes

    • Project dialog opens from the GEO project switcher; tuned tooltip delay.

Written for commit 668067f. Summary will update on new commits.

Review in cubic

Summary by Comp AI

No blocking issues found.

Written for commit 4f970c5. New commits will trigger a re-review. Generated by Comp AI.

Tracks how AI engines mention your brand: prompt scanning with grounded
engines, competitor share of voice, and the @usenotra/geo SDK with
server-side visitor classification, journey attribution and markdown link
tagging.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
notra Ready Ready Preview Aug 10, 2026 6:02pm
notra-agent Ready Ready Preview Aug 10, 2026 6:02pm
notra-console Ready Ready Preview Aug 10, 2026 6:02pm
notra-onboarding-agent Ready Ready Preview Aug 10, 2026 6:02pm
notra-web Ready Ready Preview Aug 10, 2026 6:02pm

Request Review

@cursor

cursor Bot commented Aug 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@comp-ai-code-review

comp-ai-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

Comp AI code review failed. Comment /review to retry.

Image not found.

Commit 668067f · Posted by Comp AI Code Reviews.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

React Doctor found 67 new issues in 38 files · 3 errors & 64 warnings · score 49 / 100 (Critical) · 0 fixed · vs main

Errors

64 warnings

src/app/(dashboard)/[slug]/geo/page-client.tsx

  • ⚠️ L126 React Compiler can't optimize this set-state-in-effect

src/components/charts/funnel-chart.tsx

  • ⚠️ L4 Full Framer Motion import use-lazy-motion
  • ⚠️ L595 Boolean prop combinations are hard to test no-many-boolean-props
  • ⚠️ L777 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L788 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/charts/use-enter-complete.ts

  • ⚠️ L16 React Compiler can't optimize this set-state-in-effect

src/components/evilcharts/ui/echarts-legend.tsx

  • ⚠️ L25 Non-component export in component file only-export-components
  • ⚠️ L35 Non-component export in component file only-export-components

src/components/geo/ai-traffic-card.tsx

  • ⚠️ L102 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L104 Missing effect dependencies exhaustive-deps
  • ⚠️ L108 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/competitor-detail-view.tsx

  • ⚠️ L72 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L76 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L78 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/competitor-logo.tsx

  • ⚠️ L17 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/competitor-share-card.tsx

  • ⚠️ L29 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/competitors-table.tsx

  • ⚠️ L66 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L68 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L80 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L82 Chained array iterations js-combine-iterations
  • ⚠️ L83 Array lookup inside a loop js-set-map-lookups
  • ⚠️ L88 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/directions/direction-cockpit.tsx

  • ⚠️ L104 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/directions/direction-engine-bars.tsx

  • ⚠️ L21 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/directions/direction-leaderboard.tsx

  • ⚠️ L46 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L112 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/directions/direction-pages-table.tsx

  • ⚠️ L21 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/directions/prompt-results-table.tsx

  • ⚠️ L52 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/engine-rate-table.tsx

  • ⚠️ L74 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L76 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/geo-aliases-dialog.tsx

  • ⚠️ L17 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/geo-competitors-dialog.tsx

  • ⚠️ L104 State only used in handlers rerender-state-only-in-handlers

src/components/geo/geo-languages-dialog.tsx

  • ⚠️ L56 React Compiler can't optimize this set-state-in-effect
  • ⚠️ L57 State adjusted after a prop changes no-adjust-state-on-prop-change

src/components/geo/geo-settings-dialog.tsx

  • ⚠️ L70 React Compiler can't optimize this set-state-in-effect

src/components/geo/geo-string-list-dialog.tsx

  • ⚠️ L47 React Compiler can't optimize this set-state-in-effect
  • ⚠️ L48 State adjusted after a prop changes no-adjust-state-on-prop-change

src/components/geo/geo-summary-stats.tsx

  • ⚠️ L21 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/journey-detail-dialog.tsx

  • ⚠️ L47 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L48 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/journeys-card.tsx

  • ⚠️ L25 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L30 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/mention-rate-card.tsx

  • ⚠️ L103 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/mention-trend-card.tsx

  • ⚠️ L17 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L22 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L27 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

src/components/geo/model-usage-card.tsx

  • ⚠️ L21 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L22 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L26 Manual memoization in compiler-managed code react-compiler-no-manual-memoization
  • ⚠️ L31 Manual memoization in compiler-managed code react-compiler-no-manual-memoization

14 more warnings not shown.

Reviewed by React Doctor for commit 668067f. See inline comments for fixes.

@vercel
vercel Bot temporarily deployed to Preview – notra-web August 10, 2026 16:49 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-agent August 10, 2026 16:49 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-onboarding-agent August 10, 2026 16:49 Inactive
@vercel
vercel Bot temporarily deployed to Preview – notra-console August 10, 2026 16:49 Inactive

@comp-ai-code-review comp-ai-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found across the changed files.

Commit 4f970c5 · Posted by Comp AI Code Reviews.

Base automatically changed from feat/social-analytics-clickhouse to main August 10, 2026 17:13
@mezotv

mezotv commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

@greptile

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds end-to-end GEO visibility tracking, including project configuration, AI-engine scans, traffic ingestion, journey attribution, dashboards, and a reusable tracking package.

  • Adds authenticated GEO oRPC procedures, scan workflows, and Tinybird-backed analytics.
  • Adds the @usenotra/geo package and integrates its Next.js proxy into the public web app.
  • Adds GEO database schema and migration state plus dashboard pages for visibility, prompts, competitors, traffic, and journeys.

Confidence Score: 3/5

The PR should not merge until project-scoped ingest credentials are validated and duplicate GEO scans are prevented.

Invalid project identifiers can be embedded in working ingest tokens and produce unreachable analytics data, while concurrent scan starts independently execute billable checks and append duplicate metric rows.

Files Needing Attention: apps/dashboard/src/lib/orpc/routers/geo.ts, apps/dashboard/src/lib/workflows/start.ts, apps/dashboard/src/lib/geo/scan.ts

Important Files Changed

Filename Overview
apps/dashboard/src/lib/orpc/routers/geo.ts Adds the complete authenticated GEO API surface, but ingest token issuance does not validate project ownership.
apps/dashboard/src/lib/workflows/start.ts Adds GEO workflow dispatch without a project-level single-flight or idempotency guard.
apps/dashboard/src/lib/geo/scan.ts Implements AI-engine scanning and append-only result ingestion, making duplicate workflow starts observable in metrics and cost.
apps/dashboard/src/lib/geo-ingest/pipeline.ts Authenticates tracking tokens, validates payloads, classifies traffic, assigns journeys, and ingests analytics events.
packages/geo/src/tag-response.ts Adds crawler-specific link tagging through an internal origin fetch and reconstructed response; no independently publishable defect was established.
packages/db/migrations/0063_parallel_marvex.sql Adds the persisted GEO project, settings, prompt, competitor, and sequence structures required by the feature.

Sequence Diagram

sequenceDiagram
  participant User as Dashboard user
  participant RPC as GEO oRPC
  participant Workflow as GEO scan workflow
  participant Engines as AI engines
  participant Tinybird as Analytics ingest
  User->>RPC: Start project scan
  RPC->>Workflow: startGeoScanRun
  Workflow->>Engines: Run prompt checks
  Engines-->>Workflow: Responses and mentions
  Workflow->>Tinybird: Append mention-check rows
  User->>RPC: Load GEO dashboard
  RPC->>Tinybird: Query project metrics
  Tinybird-->>RPC: Visibility, traffic, and journeys
Loading

Reviews (1): Last reviewed commit: "merge: drop analytics backfill script" | Re-trigger Greptile


return {
ingestUrl: buildGeoIngestUrl(),
token: buildGeoIngestToken(input.organizationId, input.projectId) ?? "",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unvalidated project-scoped ingest tokens

When an organization member requests ingest setup with a stale, fabricated, or foreign projectId, this handler signs that value without resolving it against the organization. Subsequent events are accepted under an invalid organization/project pairing but cannot be retrieved through project-scoped dashboard queries, contaminating the analytics stream with unreachable data.

Knowledge Base Used: Dashboard App Core Structure

Comment on lines +121 to +128
export async function startGeoScanRun(payload: {
organizationId: string;
projectId?: string;
}): Promise<{ runId: string }> {
const parsed = geoOrganizationInputSchema.parse(payload);
const run = await start(geoScanWorkflow, [parsed]);
return { runId: run.runId };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Non-idempotent project scan dispatch

If two scan requests for the same project overlap or the workflow endpoint receives a duplicate delivery, each invocation starts an independent workflow with a fresh scan ID. Both runs execute the same billable AI checks and append separate rows to the non-deduplicating datasource, causing duplicate spend and double-counted visibility metrics.


useEffect(() => {
if (!ready) {
setStage(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-hooks-js/set-state-in-effect (warning)

This synchronous effect update causes an extra render: Calling setState synchronously within an effect can trigger cascading renders. Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with // react-doctor-disable-next-line react-hooks-js/set-state-in-effect.

Fix → Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:

  • Update external systems with the latest state from React.
  • Subscribe for updates from some external system, calling setState in a callback function when external state changes.

Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).

Docs

"use client";

import type { Transition } from "motion/react";
import { motion, useTransform } from "motion/react";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/use-lazy-motion (warning)

Importing "motion" ships about 30 kb of extra code and slows page load. Use "m" with LazyMotion instead.

Fix → Use import { LazyMotion, m } from "framer-motion" with domAnimation features. Saves about 30kb.

Docs


// ─── Label overlay ──────────────────────────────────────────────────

function SegmentLabel({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/no-many-boolean-props (warning)

Component "SegmentLabel" takes 4 on/off props (isHorizontal, showValues, showPercentage…), which is hard to combine & test. Split it into smaller components or named variants.

Fix → Split boolean-heavy APIs into smaller components or named variants so combinations stay testable.

Docs


const isControlled = hoveredIndexProp !== undefined;
const hoveredIndex = isControlled ? hoveredIndexProp : internalHoveredIndex;
const setHoveredIndex = useCallback(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this function automatically. Verify that removing useCallback preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

[isControlled, onHoverChange]
);

const measure = useCallback(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this function automatically. Verify that removing useCallback preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

}

export function MentionRateCard({ engines }: MentionRateCardProps) {
const families = useMemo(() => groupEngines(engines), [engines]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this value automatically. Verify that removing useMemo preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

import { buildMentionRateRows } from "@/utils/geo-charts";

export function MentionTrendCard({ points }: MentionTrendCardProps) {
const { rows, engines } = useMemo(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this value automatically. Verify that removing useMemo preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

[points]
);

const series = useMemo(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this value automatically. Verify that removing useMemo preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

[engines]
);

const config = useMemo(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this value automatically. Verify that removing useMemo preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

import { tableHeightFor } from "@/utils/table";

export function ModelUsageCard({ usage }: ModelUsageCardProps) {
const models = useMemo(() => usage?.models ?? [], [usage]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

React Compiler can cache this value automatically. Verify that removing useMemo preserves behavior before simplifying it.

Fix → Profile compiler-managed code and remove useMemo, useCallback, or memo only when the manual cache no longer carries behavioral or performance intent.

Docs

Comment on lines +69 to +77
const withInlineLinks = text.replace(
INLINE_LINK,
(match, bang: string, label: string, target: string, title: string) => {
if (bang === "!" || !isTaggableTarget(target, host)) {
return match;
}
return `[${label}](${appendJourneyParam(target, journeyId)}${title})`;
}
);
Comment thread packages/geo/src/send.ts
const TRAILING_SLASHES = /\/+$/;

function ingestUrl(endpoint: string | undefined): string {
const base = (endpoint ?? DEFAULT_ENDPOINT).replace(TRAILING_SLASHES, "");
export async function geoScanWorkflow(
payload: GeoScanPayload
): Promise<GeoScanResult> {
"use workflow";
organizationId: string,
projectId?: string
): Promise<GeoScanResult> {
"use step";
import { Loader2Icon } from "lucide-react";
import { useState } from "react";
import { CompetitorLogo } from "@/components/geo/competitor-logo";
import { findCompetitorDomain } from "@/lib/geo/domain";
}: GeoCompetitorsDialogProps) {
const [draft, setDraft] = useState("");
const [domainDraft, setDomainDraft] = useState("");
const [selected, setSelected] = useState<string | null>(null);
} from "@/types/geo-directions";

const PERCENT = 100;
const MIN_BAR_PERCENT = 3;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants