AI Visibility polish — scans, prompts, and honest marketing
AI Visibility page: view latest scan answers per engine, add and archive prompts manually, upgrade prompt for Free plans, and clearer quota/rate-limit messages.
⌘K command palette is back on the dashboard — jump to AI Visibility, AI Traffic, and every other screen.
Weekly AI Visibility scans now run on a Sunday cron for eligible Pro+ sites.
Pricing, comparison pages, and public docs updated to match what the product actually ships today.
Cobalt everywhere — emails, sign-in, badges, a denser layout
The cobalt refresh now reaches every corner: transactional emails, the browser/PWA theme color, embeddable badges, the sign-in screen, and the error page all match the new look (semantic greens for “good/healthy” were kept green on purpose).
The dashboard is a touch denser — tighter card padding, spacing, and row heights — so you see more at a glance.
The Site Audit's findings are now a scannable signal-row list — a severity dot, the finding, and how many pages it affects. Click any one to read the full detail, the fix, and the affected pages in a side panel — the same fast pattern as the Action Queue and Chats.
The Chats timeline now uses the same fast pattern as the Action Queue: each AI answer is a scannable row with a clear “Mentioned” signal, and clicking it opens the full answer + the brands it named in a side panel.
The Action Queue is now a dense, scannable list: each recommendation is a single row with a priority status dot, type, and value — far faster to triage at a glance.
Click any row and its full detail opens in a panel right beside the list, instead of loading a separate page — review and act (Accept · Done · Snooze · Dismiss) without ever losing your place.
Color now carries meaning consistently: AI / generative surfaces read in purple, and live / realtime indicators in cyan — so you can tell signal types apart at a glance.
PerchLens now opens in dark mode by default, matching the refreshed look. You can still switch to light anytime from the theme toggle — your choice is remembered.
Surfaces moved to a cooler, more technical gray for a calmer, more focused workspace.
PerchLens has a fresh visual identity: a confident cobalt-blue accent in place of emerald, the clean Geist typeface across the whole product, and crisper near-square corners. This is the first step of a broader design refresh rolling out over the next releases.
Trend arrows now read green for up and red for down independently of the accent color, so direction is never ambiguous.
The dashboard now flows in a more useful order: Busy times sits right under your traffic trend, with Geography directly below it.
Geography (on the dashboard and the Globe page) now draws a lightweight 2D world map of where your visitors are — a dot per country sized by visits, hover for the count. Rebuilt as pure SVG after the earlier map/globe libraries proved unreliable.
Realtime: the visitor stream now shows proper country flags instead of raw country codes.
New Globe page (Analytics → Globe): a live, rotating 3D globe of where your visitors are right now, with a glowing marker on every country sized by its sessions. Drag to spin it.
Prefer flat? One click switches to a 2D world map shaded by sessions, with hover tooltips per country.
A live Sessions panel shows visitors as they arrive — country, page, and source — and a Top countries rail ranks your geography at a glance.
Everything is real, period-aware data — no sample points. Falls back to the 2D map automatically on devices without WebGL.
Two clear products, one switch — Analytics and AI Visibility
Your dashboard is now two focused products under one login. A switch at the top of the sidebar flips between Analytics (your website traffic) and AI Visibility (your GEO / search + AI-answer optimization) — each with its own short, single-purpose menu instead of one long mixed list.
The AI Visibility side groups everything in one place: the AI-engine overview, tracked prompts, the Action Queue, competitors, SEO, and reports.
Nothing was removed — it's just organized so neither product buries the other, and each menu stays short and clear.
Dashboard flows as one scroll; AI insights unified; live busy-time marker
Your site dashboard is now one natural scroll — we removed the Overview / Audience / Acquisition / Engagement / AI tabs, which were showing overlapping copies of the same cards. Everything that matters is on one page, most decisive first.
Top Pages and Top Sources now link straight into their full pages (“See all →”) — the dashboard gives you the highlights, the dedicated pages give you the depth.
AI, unified: the engine-by-engine deep-dive now lives on the AI Visibility page as a “By engine” tab (it used to be a hidden, unreachable page). One home for everything AI.
The Busy times chart now marks the current hour, so you can tell at a glance whether right now is a busy time.
Cleaner empty states on new sites, and the realtime “requires Pro” note is now a one-click upgrade link.
A calmer, tidier dashboard — one top bar, clearer navigation
Redesigned the dashboard chrome: a single slim top bar on every page with a breadcrumb (where you are) and one global date-range filter — replacing the old stacked headers, so pages open straight into your data.
New date filter in the top bar — switch Today / 7 days / 30 days / 3, 6, or 12 months from one place, on any page.
Reorganized the sidebar into clear groups — Analytics, Visibility, and Workspace — so every page has an obvious home.
Brought the Visibility pages (Action Queue, SEO, AI Visibility and more) in line with the rest of the app for one consistent, calmer design language.
Lighter and faster under the hood — removed a large amount of dead code and a duplicate page.
Dashboard, reimagined — most important first, daily by default, new long ranges
Your main dashboard now opens on Daily by default when you pick a site, with a new range switcher: Day, Week, Month, plus 3-, 6-, and 12-month views.
Reprioritized so the most important things come first — headline KPIs and your traffic trend, then AI visibility (which engines are sending you visitors) alongside your top SEO/GEO fixes, then a plain-English 'what changed' summary, then who's on the site right now.
Top fixes: the three highest-priority open recommendations from your Action Queue now surface right on the dashboard, one click from the full queue.
Live signals: a 'now' indicator on Visitors plus a live per-minute pulse, so the numbers feel as current as your traffic.
Built lean — it reuses your existing analytics and AI summaries (cached so it stays near-free) and keeps every label honest.
Add-site flow, real-time, and shared dashboards — wired end to end
Adding a site now works end to end — the setup wizard creates your site, gives you the install snippet with your real tracking ID, and watches live for your first event.
Real-time now shows true last 1m / 5m / 30m visitor counts and a live per-minute pulse, derived from your actual event stream.
Shared (public) dashboards now render the traffic-over-time chart, real period-over-period deltas, and KPI sparklines instead of blank placeholders.
AI traffic band shows a real week-over-week change; the per-engine figure is hidden where there isn't enough history to compute it honestly.
Heatmaps: real device segmentation (desktop / mobile / tablet) from click data, a real page picker, and honest 'coming soon' states for movement / scroll / rage.
Tidied a couple of presentational placeholders — removed a non-functional heatmap time slider and corrected the Sites workspace label.
Wiki — the PerchLens knowledge base in copyable Markdown
New public Wiki at /wiki — the complete PerchLens knowledge base (overview, getting started, analytics, AI traffic & GEO, the Action Queue, integrations, privacy, plans, FAQ, and a glossary) in clean, structured Markdown.
Built for the AI era: copy any section as Markdown, copy the entire wiki in one click, or download it as a single file. Fetch it directly at /wiki.md (curl-friendly) to feed into any AI assistant as grounding context.
TechArticle structured data + sitemap inclusion so the knowledge base is itself GEO-friendly — practicing what PerchLens preaches.
Settings now has a sticky section nav — jump straight to Tracking, Reports, Notifications, Integrations, Sharing, Filters, or Danger Zone instead of scrolling the whole page
Normalized vertical spacing across dashboard pages so the rhythm is consistent (Search & SEO and the Visibility hub now match their siblings)
Faster, leaner landing page — trimmed unused component CSS (~190 KB), scoped the auth + support bundles to the pages that need them, and stopped a background canvas from over-rendering on long pages
AI action quota, AppSumo lifetime tiers, IA refresh, pricing redesign
Monthly AI action quota — Pro 100 / Studio 500 / Free 0. Each Ask AI, Action Queue Generate, GEO refresh, topic regenerate, prompt run, or anomaly Explain consumes one action. Resets on the 1st of every month. Live counter + progress bar live in Settings.
AppSumo lifetime tier scaffolding (stackable T1 / T2 / T3, best-tier-wins). Schema, redemption flow, and LTD-aware AI quotas all wired through; activation gated on actual deal launch.
New /appsumo landing page — three lifetime tier cards, stacking explainer, AI-action explainer, FAQ. Linked from the marketing footer and the /pricing trust strip.
Sidebar IA cleanup — trimmed from 17 entries to 10. Demoted destinations (Integrations, Heatmaps, Compare, Visibility sub-pages) stay reachable via contextual cross-link strips on their parent pages, the new /visibility hub, the command palette, and the mobile More menu.
Visibility hub at /visibility — summary tiles + quick-link cards to Action Queue, Topics, Competitors, GEO Tracker, Reports, and Setup. Setup form moved to Settings.
/pricing redesign — solid brand pill replaces the gradient on the featured card, honest CTAs (Start free / Get Pro / Get Studio), per-card audience eyebrows, grouped comparison table (Capacity / Analytics / AI / Workflow), new trust strip + AI-action explainer.
/roadmap surfaced as a credible public commitment — 21 seeded items across shipped, in-progress, planned, and proposed buckets, aligned to the actual changelog.
/vs/* comparison pages updated with the new Visibility action queue as a top-of-table feature row.
Backend-leak copy scrubbed across Settings and Visibility surfaces (no more "Drizzle", "deterministic pass", "configured LLM", specific anomaly thresholds, or internal plan slugs in user-facing text).
Density pass across the Visibility section — tighter paddings, list-row heights, and section gaps for a Linear-style read.
Action Queue URL filters — ?priority= and ?status= now apply on mount; selections survive refresh and copy-paste.
New Visibility section in the sidebar with Overview, Action Queue, Topics, Competitors, GEO Tracker, and Reports routes
Persisted, prioritized recommendations with status management (new, accepted, in_progress, done, dismissed, snoozed) — your triage survives every regenerate
Recommendation generator combines a deterministic pass over your latest crawl with an AI pass over your niche, top pages, GSC queries, and competitor topics
Topic clusters detected from the latest crawl and named via AI; pillar + supporting + missing-page suggestions per cluster
Competitor tracker (Pro 5, Business 20) with light-fetch topic detection and gap surfacing into the action queue
GEO prompt tracker — seed 10–20 niche-derived prompts and run heuristic visibility estimates against tracked competitors
Weekly visibility reports — counts of new / done / dismissed plus the top 5 high-priority open items
Engine reuses the existing crawler, AI provider chain, GSC integration, and analytics infrastructure — no new heavy dependencies
Security and reliability fixes for analytics and share dashboards
Analytics batch and public share routes now fail closed when the app database is unavailable, matching the main analytics API and closing IDOR and gate-bypass edge cases
Password-gated share dashboards use opaque signed session cookies instead of storing the password hash in the browser cookie
Generic annotation webhooks now require Authorization: Bearer only (query-string tokens removed)
Analytics API failures return proper HTTP error codes; dashboard hooks surface errors instead of silently showing empty data
Hardening pass — accurate numbers, manageable events, a cleaner dashboard
Pinned chart events are now fully manageable — click any event chip to edit its label, date, or notes, or remove it behind a two-step inline confirm. Pinning used to be one-way: a typo'd label was permanent and clearing an event meant a raw API call. The Pin-event dialog also picked up a layout fix — on desktop its header, form, and actions had been rendering as three squashed columns
Conversions now count what they should — behavioural events were being miscounted as conversions, and element-click and custom-event goal progress weren't being counted at all. Both are corrected, and several dashboard metrics that showed placeholder or wrongly-computed values now reflect real data
GEO and SEO recommendations are downloadable — take the full list out of the dashboard to share with a teammate or work through offline
Delete a site you no longer track straight from the sites list, instead of leaving it orphaned in the switcher
Adding a site is more reliable: a missing production migration that had been breaking site creation outright is applied, the form now surfaces the real error when something fails instead of a generic message, and the www and non-www versions of a domain are treated as one site rather than two
The onboarding wizard reliably restores its place if you reload mid-setup, and the "verify your tracking snippet" check polls more dependably
An accessibility and visual-consistency sweep across the dashboard — contrast ratios, keyboard focus indicators, ARIA labelling, and consistent design-token usage so light and dark modes stay coherent
Fixed a dashboard view that could crash on certain renders — a React hook was running conditionally — and added a build-time ESLint guard so that whole class of bug can't ship again
An end-to-end bug hunt closed a date-boundary case that could double-count a day of traffic, an auth check that failed open, and two API routes that could return a 500 on malformed input
Under the hood: dropped six unused dependencies and removed dead starter-kit files, scoped a cron query that had been loading an entire table on every run, and extended the schema-drift checker to cover more tables
Annotation auto-imports — release tags + deploys land on every chart automatically
GitHub Releases webhook: paste your perchlens annotation URL into any repo's webhook settings and every published release becomes a labelled vertical line on every chart on the site. Tag-named, described with the release title, drops within seconds of `gh release create`
Vercel deploys webhook: same idea, for production deploys. "Deploy · main" lines tag every successful production deploy, described with the first line of the commit message. Preview deploys and errored deploys are ignored — only the events worth marking get marked
Generic webhook: POST JSON to your site's annotation URL with a bearer token and we'll create the annotation. Body is `{ label, description?, occurredAt?, externalRef? }` — wire Zapier, cron, CI, or anything that can POST. The Twitter campaign launch at 9am Tuesday is one curl away
New Settings → Integrations panel — one shared per-site webhook secret, three copyable URLs, brief setup steps inline for each integration, and a rotate-secret button with two-step confirmation. The secret is masked by default so screenshotting the panel doesn't leak it
Pinned-events chip strip now shows source affordance: small Pin icon for events you pinned manually (brand emerald), small GitBranch icon for GitHub releases (muted), Rocket for Vercel deploys (muted), Lightning for generic webhooks (muted). Tooltip on each chip leads with the source label so screen readers + hover both get the context
Mobile bottom sheet: "How AI traffic is detected" modal now renders as a Vaul drag-to-dismiss bottom sheet on phones, joining Pin event and Add conversion. Last centred modal in the dashboard cleared
Monthly digest emails get their own template — subject becomes "<Month> in review — <site>" instead of the weekly-looking "Weekly · …" prefix that the shared template was producing; eyebrow and CTA copy are also monthly-framed. The data shape and the editorial typography pass from Day 25 are unchanged
Under the hood: shared HMAC-SHA256 / HMAC-SHA1 / bearer-token verifiers in src/lib/webhook-secret.ts with constant-time comparison; raw-body reading on all three webhook routes (re-parsing JSON would break signatures); dedup by externalRef so idempotent retries from GitHub/Vercel update in place instead of duplicating
The AI Era release — a dashboard that narrates itself
GEO recommendations now stream — the page no longer sits on a 30-second "thinking…" spinner; each recommendation card renders as the model emits it, line by line, so you see the first idea before the third one finishes generating
Every analytics surface (Overview, Realtime, Sources, Pages, GEO) now opens with a one-paragraph narration that streams in — "Mobile share jumped 11pts this week; ChatGPT referrals doubled; your /pricing page lost 8% of converts." Reads the data, finds the story, says it plainly. New <InsightBanner surface="…"/> primitive — one prop swaps the voice per page
Anomaly banners gain an "Explain" button that streams a Claude-narrated likely cause in plain English — pulls today's vs yesterday's top pages as context, returns a 60-word paragraph, rate-limited 10/hour per user
Cmd+K (or just "/") now streams the answer to whatever you ask about your data — tokens render as they arrive instead of waiting for the full reply. Suggested follow-up questions appear under each answer, and your last 6 questions persist across sessions
Weekly + monthly email reports now lead with your single highest-impact GEO recommendation — pulled from the cache the streaming endpoint already warms, so emails stay free and fast. If you haven't opened /geo this week the block omits silently
Anomaly alerts now route by severity: spikes go to webhook only (Slack/Discord), drops email + webhook, critical drops (≥95% collapse) get a 🚨 URGENT prefix and red Block Kit color. The Severity field lands in every payload for downstream filtering
Editorial typography pass on email reports — Georgia serif headings, tighter measure, soft 4%-shadow metric cards instead of hard borders, warmer cream background. Same data, reads like a Monocle column instead of a status email
Date filters are now URL-synced (?from=2026-01-01&to=2026-01-31&range=Q1+2026) — "save view" is now just "bookmark this URL." Paste a URL into Slack to share the exact dashboard state; no backend table, no extra storage
Plus: provider-agnostic streamClaude() under the hood streams across OpenRouter → DeepSeek → Anthropic with the same fallback chain as single-shot calls, so a $0.0003 GEO run still costs $0.0003 streaming; SUGGESTIONS= tail-buffering hides control markers from the streamed answer mid-flight; cache hits replay as synthetic streams so cached and live render identically
Why v2: this is the AI Era release — the first version where the dashboard talks back. Everything else is groundwork.
Canonical <EmptyState> primitive — every "No data yet" / "No goals yet" / "No sites match" screen now speaks the same voice, with a brand→accent halo behind the icon, tooltips on long labels, and proper CTAs where they belong
First-goal flow on /conversions now surfaces the goal suggestions scraped from your site during onboarding — "Track visits to /pricing" instead of generic templates
Tier rename: Business → Studio. Pro/Studio is more distinctive than the Plausible-tier "Pro/Business" cliché; a future Agency tier is architected in the schema as released:false so we can ship it later without a rebuild
Universal site importer at /migrate — paste a list of domains (or CSV with domain,name headers) and we create matching PerchLens sites in one shot, deduped, plan-limit-aware, partial-success-friendly
Beam Analytics sunset (Sep 1, 2026) gets a migration banner above the standard /vs/beam comparison with a CTA into the importer pre-filled for that source
Live-visitors widget now streams via Server-Sent Events instead of polling every 2 minutes — sub-5-second freshness on the active count + recent events feed for Pro+, with a graceful single-pulse close on free so the upgrade is felt
Chart annotations: pin events ("v2.0 launch", "Twitter post", "press hit") to a date and render them as labelled lines on every chart on that site. New endpoint, hook, modal, pinned-events strip on /all-in-one. Schema supports four ingestion sources (user-pinned, GitHub Releases, Vercel deploys, generic webhook) — auto-imports next sprint
ResponsiveDialog primitive — modals now render as native-feeling bottom sheets on phones (Vaul, with grab handle + drag-to-dismiss + safe-area-aware padding) and the existing centred dialog on tablet+. Pin event + Add conversion are the first two surfaces migrated
Plus: middleware allowlists the new /migrate route; <ReferenceLine> support added to TrendChart so any future chart inherits annotation rendering for free
New emerald PerchLens brand mark (an eye motif inside the 'P') replaces the placeholder squircle across favicon, apple touch icon, OG image, footer, in-app logo, and email reports
Full PWA install support: manifest now ships any-purpose + maskable icon variants in 192/512/180 — meets Chrome and Android installability spec
PWA install nudge: subtle bottom-right banner appears 8 seconds after dashboard mount on supported browsers; prompts native install on Chrome/Edge/Brave/Samsung, shows iOS Add-to-Home-Screen instructions on Safari, dismissible and remembered
Dropped Lobster Two font (only used by deleted Satori OG generators) — saves ~235KB from the build and one fewer Google Fonts request on first load
Email-report header now uses the real brand mark image instead of the inline 'PL' placeholder
A11y: focus-visible on the last 9 unfocused buttons
Added focus-visible outlines to onboarding-wizard close, onboarding-banner dismiss, section-error retry, zoomable-map reset zoom, landing FAQ accordion, and four buttons in site-audit-section (toggle, insight filter, load past results, schedule frequency)
These had escaped earlier audit passes because they had no focus styling at all (not even a base focus: pseudo-class) — the deeper grep this round caught them
Resolves the last WCAG 2.4.7 Level AA gaps in the dashboard and marketing surfaces
Security: explicit deny-all RLS policy on cv_events
Resolves Supabase Advisor INFO warning rls_enabled_no_policy on cv_events
Adds a RESTRICTIVE policy that denies all anon + authenticated access — functionally identical to the empty policy set we had before, but the linter now sees a policy exists and future maintainers get a clear signal that the design is intentional
Service role (ingest) and SECURITY DEFINER functions (analytics_query, ai_trend_query) continue to bypass RLS — no functional change for the app
Security: revoke public access to SECURITY DEFINER analytics functions
Resolves two Supabase Advisor warnings (anon_security_definer_function_executable + authenticated_security_definer_function_executable) on analytics_query and ai_trend_query
REVOKE EXECUTE from anon and authenticated roles on both functions
Service-role calls (every legitimate path through our app) keep working — they bypass GRANT/REVOKE entirely
External /rest/v1/rpc/analytics_query and /rest/v1/rpc/ai_trend_query calls with the public key now correctly return 403
Third audit pass — broad tap-target sweep, theming, polish
Extended tap-44 mobile hit-area utility to ~16 more icon buttons across pages, sources, realtime, geo, sites, settings, anomaly banner, first-insight banner, web vitals, usage bar, theme toggle, toast, site audit, and onboarding wizard
All 3 decorative imgs in empty-state get loading="lazy" (was 1 of 3)
Textarea resize-handle hex extracted to named constants (RESIZE_HANDLE_LIGHT/DARK) with explicit comment explaining why CSS vars can't expand inside SVG data URLs
Documented the tap-44 pattern in globals.css with a usage example so new icon buttons stay in compliance
Removed dead fade-in-up keyframe + animate-page-enter utility (PageTransition uses motion/react, the CSS fallback was scaffolded but never wired)
Security: enable RLS on every public Supabase table
Resolves both Supabase Advisor critical alerts (rls_disabled_in_public + sensitive_columns_exposed)
New migration 20260501000000_enable_rls_all_public.sql sweeps every public table and enables RLS — covers cv_events plus any tables added since the v1.6.x cv_events fix
No new policies required: app traffic uses service_role (bypasses RLS) for ingest and SECURITY DEFINER functions (analytics_query, ai_trend_query) for reads; both bypass RLS by design
Cleanup: deleted a misplaced Neon migration (webhooks_anomaly.sql) that had been wrongly placed in the Supabase folder and was failing every push
Second audit pass — bundle splitting, tap targets, lazy images
CommandPalette + ShortcutsModal now dynamic-import — defers ~40KB of Cmd+K and shortcuts UI for the 90% of dashboard visits that never trigger them
Settings live-visitor-badge image gets loading="lazy" — was the last below-the-fold img missing it
Extended tap-44 mobile hit-area utility to icon buttons across the dashboard topbar, AI detection modal, command palette, onboarding banner, and theme toggle
Landing header drops backdrop-blur for solid bg-primary — quieter, more honest, matches the brand voice
Conversions page rebuilt: unified list (replacing the split goals/conversions view), inline edit, 8 preset templates, multi-step create dialog with autocomplete and live code snippets
Conversion 'Resets' toggle: choose monthly reset or running total per goal
Tracking script v2.0: data-track attribute click tracking, automatic form_submit detection, scroll-depth events at 25/50/75/90%, file-download detection (pdf/zip/docx/etc.), and copy event with character length only
MetricRow and StatCard now accept a tip prop that renders an inline help tooltip — wired across the all-in-one dashboard
Always-visible explainer strip on the Conversions page so beginners understand what conversions are