BETAPro is not billed during beta. Lock in the price and we will honor it at launch.Freeze this price

Changelog

Stay up to date with the latest changes in our product.

July 28, 2026

IA restructure: Dashboard / Insights / Recommendations

  • Launch-polish (research-backed): Dashboard toolbar now carries a "Data as of …" freshness stamp (newest scan time); the first-run empty hero is now a 4-step activation checklist with progress (add brand → add queries → run first scan → read first report), each step linked and state-derived from workspace counts; /recommendations got a content-shaped route skeleton (Next.js loading.js pattern).
  • New "Recommendations" hub (replaces the Analytics nav item): every stored AI recommendation in one place — cross-report findings with cadence tabs (Weekly / Monthly / Long-term), Open/Acted filters, per-finding source-report links, and the mark-as-acted + measured-outcome loop. Below it, the deterministic opportunity views (opportunity map, "Sources to win" gap domains, uncited prompts) as pre-filtered 30-day views. Zero LLM calls on load.
  • Weekly digest findings are now first-class: the Monday digest's narrative is persisted onto a real 7-day snapshot row (was notification-only), so weekly findings are archived, markable, and appear in the hub. Digest notification links now use the org slug (canonical) instead of the raw id.
  • Analytics page dissolved: sentiment-by-brand + sentiment-over-time moved to Insights; the opportunity map moved to the hub; /analytics redirects to /recommendations for one release.
  • New dashboard.recommendationsFeed read procedure (hash-dedupe of inherited narratives, acted-copy-wins; outcomes derived server-side); outcomeSinceAction moved to @repo/utils.

July 28, 2026

Findings that close the loop: acted-on tracking + past-reports archive

  • "Mark as acted on" on every insights finding (members+, viewers read-only). Once marked, the finding shows the measured outcome: mention-rate movement since the action date, derived live from stored snapshots — before/after numbers, deliberately not an attribution claim. Migration 0045 (finding_action).
  • Past reports (Insights): every AI report was already retained forever — now it's readable. A dated archive lists prior narratives; each opens in a sheet with its summary, findings (+ acted markers), the KPIs it saw then, and the mention-rate movement since.
  • Insights responses now carry snapshotId; on the stale-narrative path actions key to the report that owns the findings, not the newer data snapshot.

July 28, 2026

Demo workspace: every new user starts with a populated product

  • Auto-seeded demo workspace at onboarding: founders get a second org, "Auralane (demo)" — a fictional CX-software brand (all 8 names collision-checked) with 39 real-shaped scans across 14 days, 143 stored LLM responses, mentions, citations (distinct .example domains so citation analytics stay correct), rollup cells replayed through the real pipeline, and AI reports shipped in the fixture. Persistent amber "Demo workspace" banner; deletable anytime; invited users never get one.
  • Zero spend, enforced everywhere: scan preflight skips demo orgs (covers execute/bulk/scheduled), retry + schedule-upsert blocked, narrative regenerate + digest/topic batch submits rejected server-side AND hidden in the UI, LLM crons exclude demo orgs. Seeding itself makes no LLM calls.
  • Seeding runs async via Inngest (demo-org-seed, idempotent + self-healing); fixture exported by pnpm --filter @repo/scripts export:demo-fixture with a real-brand-name leak guard, regression-tested against the shipped fixture.

July 28, 2026

Dashboard visualization upgrade (competitive-research round)

  • Leaderboard combo (Dashboard + Insights): donut headline showing your appearance rate, rank-change arrows (first vs last reliable day of the window), an inline trend sparkline per competitor row, and a Share % / Mentions toggle. Rows beyond the trend series degrade to an honest dashed baseline.
  • Sentiment by brand (Analytics): one 100% bar per brand segmented positive/neutral/negative — every competitor's sentiment mix in one glance. Denominators labeled as _classified_ mentions (sentiment tagging runs on BYOK scans).
  • Sentiment over time (Analytics): stacked 100% bars per day/week showing how your brand's sentiment mix is shifting; periods with no classified mentions render as gaps, never fabricated 0/0/0 bars.
  • New Analytics page (sidebar → Analytics): dedicated home for the exploratory charts below — Dashboard stays the operational overview, Insights stays the AI-narrated report.
  • Opportunity map (Analytics): every saved query plotted importance × mention rate with quadrant labels — "high importance · low visibility" top-left is the act-here zone; dot size = scan volume, thin-data dots faded. The one visualization none of the competitor tools shipped.
  • API: new sentimentByBrand, sentimentTimeseries, queryOpportunities read procedures (single GROUP BYs over analytics_daily); sovTimeseries buckets now carry raw counts alongside rates.

July 28, 2026

Cost page: instant reads + admin "Update data" button

  • COST_READ_FROM_ROLLUP verified safe (parity gate reconciled at Δ $0.0000 after fixing a stale comparator) — the Cost page now reads precomputed cost_ledger_daily cells instead of walking raw rows per open. New spend still appears immediately (un-rolled days are topped up live).
  • Admin-only "Update data" button on the Cost page re-rolls the workspace's daily cells on demand — closes the ≤24h window where reconcile-spend settles costs _after_ the nightly rollup sealed them. Bounded, LLM-free, org-scoped.

July 27, 2026

Read-only Viewer role

  • New workspace role: Viewer — full visibility (dashboard, scans, insights, notifications), zero mutations. Invitable from Settings → Members alongside Owner/Admin/Member.
  • Server-enforced: every mutating or LLM-spending org procedure (run/bulk/schedule scans, brand/topic/query CRUD, insights + topic-narrative regeneration, chatbot, alias suggestions, source recommendations, scan notes, workspace logo) rejects viewers with a clear "read-only" error. Read paths — including cached insights — stay fully open.
  • Primary run-scan CTAs are hidden for viewers (dashboard toolbar, scans page); /scans/new and /scans/bulk redirect viewers back to the scans list. Remaining edit affordances rely on the server rejection + toast until the full UI pass.
  • Also fixes invited-user onboarding from earlier today: an invitee joins the inviting workspace directly instead of being forced through founder workspace creation.

July 27, 2026

Notifications page

  • New `/notifications` page in the sidebar (ANALYZE group). The bell popover was the only surface — it auto-marked everything read on open and its links jumped straight to destination pages, so there was nowhere to review what fired, when, and with which numbers. The page lists full history with filter tabs (All / Alerts / Digests / General), an unread-only toggle, and load-more pagination.
  • Expandable details. Each row expands to show the alert's stored payload — rates, deltas, thresholds, corroborating platforms — the context that was previously written to the DB but never rendered. A per-row button opens the related page (brand, dashboard, cost settings).
  • Read semantics. The bell keeps its auto-mark-on-open behavior; the page is explicit — rows mark read on expand/click-through, plus Mark-all-read. The bell popover now links to the page ("View all notifications").
  • API: notifications.list supports catalog-group + unread filters and opaque keyset pagination; response shape is now { items, nextCursor }.

July 15, 2026

Three-mode scan execution, insights spend safety, and scan UX

Scan execution — three modes, per provider

  • Every direct provider now runs one of three ways, chosen per provider in Settings → Scan execution: Router (via OpenRouter), Direct (your own key, native SDK, auto-throttled to your rate limits), or Direct-Batch (the provider's async Batch API, ~50% cheaper).
  • Direct-Batch now covers all three direct providers — Claude, GPT, and Gemini (Gemini Batch Mode via :batchGenerateContent, grounded with google_search so batch answers still carry citations). Batch scans land asynchronously (minutes to ~a day) via a */15 drain cron.
  • Per-provider Citations on/off — trade web-searched sources for cheaper parametric answers; honored in Router, Direct, and Batch. The scan pre-flight strip shows each platform's route + citations before you run.

Scan UX

  • Per-scan model picker — choose which models a scan runs (BYOK-honored; free tier runs all four). On the New Scan form, and as a compact button + modal on the Bulk run form and the /queries multi-select.
  • Per-scan note — attach a short label to a run on the scan detail page; shown there and under the title on the /scans list.
  • Batch-routed scans no longer show a misleading "Failed" while results are still processing — the detail page + list surface "Batch · processing" and a "results in ~a few hours" banner.

Insights — never spends money on page load (fix)

  • Visiting /insights (and per-topic insights) is now read-only: it loads the last AI summary from the database and never runs the model on its own. A new summary is generated only when you click Regenerate / Generate.
  • Hitting your monthly cost cap (Settings → Cost) no longer errors the page — your last summary stays on screen with a banner that explains the cap and links to raise it.

Deploy

  • New migrations 0037–0040 and a */15 process-scan-batches cron (see DEPLOY.md). Sub-daily crons require Vercel Pro; without the cron the Direct-Batch lane won't land results.

July 15, 2026

Go-live reliability hardening

  • Added a deterministic P0–P10 go-live gate, bounded local load-smoke harness, CI dependency audit, and automated real dump/restore drill.
  • Added provider-level email/webhook idempotency, remote-restore safeguards, post-restore integrity checks, and legal-draft noindex/sitemap containment.
  • Production now requires Sentry and docs URLs, and refuses to boot with billing enabled unless every Stripe control is configured.
  • Email delivery failures now propagate to authentication and notification callers instead of being reported as success; production rejects console or unsupported mail providers.
  • Production configuration validation now requires secure public URLs, managed AI credentials, and provider-specific mail credentials.
  • Database startup treats a blank TURSO_DATABASE_URL as absent so it cannot mask a valid DATABASE_URL compatibility fallback.
  • Scan and notification outboxes use atomic claim leases to prevent duplicate delivery by overlapping workers, with concurrency regression tests.
  • Cron monitoring is locked to the deployed schedule by test, production CSP is enforced by default, and deployment/operator documentation reflects the current 11-cron architecture.
  • Docs type generation now runs in a Windows-safe order so Next.js cannot truncate the generated Fumadocs server module before TypeScript checks it.
  • Removed non-functional lead-capture forms and generic starter content; launch content is English-only until the remaining translations receive product review.
  • Verified migrations, production builds, dependency audit, security headers, health checks, cron authorization, and outbox drains against an isolated local production-like database.

July 9, 2026

ScanProvider registry for 44+ providers + BYOK hardening

Architecture

  • Full registry in @repo/ai (Map + registerProvider, getDirectProviderIds, registerKeyResolver, providesSettledCostMetadata, isRouter, shouldUseSettledCost).
  • No hard-coded provider lists or === "openrouter" checks in hot paths (execute, dispatch, Inngest, costs, extractor).
  • computeUsedOurKeyForPlatforms is injectable for tests.
  • API side registers resolvers at load (proper layering).

User-facing fix

  • Fixed crash on Settings → API keys: DIRECT_BYOK_CONFIGS.map is not a function (was caused by exporting data from a "use client" module and using it in an RSC page under Turbopack).

Other

  • All hot paths, BYOK UI, costs, and tests updated to be registry-driven.
  • Extensive plan + research notes captured in .apsolut/03-plan/.
  • feat(ai): registry core
  • feat(api): dynamic execution + resolver wiring
  • feat(saas): integration + RSC data fix
  • docs: handoffs and research

July 6, 2026

Cost accuracy + spend controls (plan 043)

Cost accuracy (the ~4–6× undercount)

  • Web-search cost is now captured. estimateCostMicros is token-only, but :online/Perplexity scans are dominated by per-request search fees. We now enable OpenRouter usage accounting (usage:{include:true}) and store the settled, search-inclusive cost from result.providerMetadata.openrouter.usage.cost, falling back to the estimate only when absent. The fragile post-hoc /generation true-up becomes a backfill, not the primary path (it was failing silently — 100% when the key was dead, ~20% success on Anthropic even normally). Verified live via pnpm --filter @repo/scripts verify:settled-cost.
  • Anthropic rows no longer vanish. OpenRouter returns anthropic/claude-4.5-haiku-20251001; the pricing table keyed claude-haiku-4.5 (version in a different position), so the lookup missed → NULL cost → the row was dropped by the dashboard's isNotNull filter (63/79 rows one week, 68/68 the next). Added the returned-slug aliases.
  • New `response.costSource` (settled | estimate) records provenance; the workspace cost view surfaces an "≈ N of M calls estimated" indicator so an undercount is visible instead of silent.

Scan robustness

  • `maxOutputTokens` clamped for BYOK scans (SCAN_DEFAULT_MAX_OUTPUT_TOKENS = 4000). Previously BYOK passed no ceiling, so OpenRouter reserved credit against the model's 64k max up front and rejected requests near a key's budget edge with "requires more credits, or fewer max_tokens" — even when the key could afford a normal answer.

Notifications

  • "Your API key is out of credits / was rejected." 402 (credits) and 401/403 (auth) are now distinguished from generic provider errors (classifyScanError + scan_error.errorClass), and a new ALERT_API_KEY_ISSUE notification fires from the scan failure path — including on a fully-failed scan, where the metric alerts (which need data) don't run. Cooldown once/day/class via alert_state.

Spend controls

  • Per-org cost alert threshold. ALERT_COST_THRESHOLD now reads organization_quota.costAlertThresholdUsd (falls back to the global COST_ALERT_MONTHLY_USD env default).
  • Optional hard budget cap. organization_quota.budgetCapUsd — new scans are refused at dispatch once month-to-date spend reaches it (BudgetExceededError → HTTP 402 → existing paywall). Counts BYOK + managed spend alike. Enforcement is a pre-scan gate, so a scan already in flight can overshoot by its own cost. Both configurable on Settings → Cost (admin-only "Spend controls" card). Wired into all three dispatch paths — manual, bulk, and scheduled/cron (the last is where unattended runaway spend happens).

Reconciliation guardrail (automated)

  • Daily backend cron /api/cron/reconcile-spend (06:00 UTC, vercel.json) compares our recorded month-to-date spend against OpenRouter's authoritative usage_monthly (from GET /api/v1/key — works for the managed key _and_ every BYOK key, grouped by key). Drift > 5% AND > $0.50 is logged at error level → Sentry, and a key under 10% of its cap is logged at warn level — so we're paged automatically instead of watching a console. Shared logic lives in reconcileSpend() (packages/api/modules/scans/lib/reconcile-spend.ts); pnpm --filter @repo/scripts reconcile:spend is the CLI view of the same job. First run confirmed the incident key's historical undercount (ours $0.69 vs OpenRouter $5.40). Alerts us, not the customer — drift is our data-integrity concern. (Needs the existing CRON_SECRET; no new env.)

July 5, 2026

Citation-engine audit fixes (plan 041)

Measurement correctness (the big ones)

  • Web retrieval is now actually ON — the product's core defect: scans ran plain chat completions, so OpenAI/Anthropic/Gemini answered from parametric memory with structurally zero citations and every citation surface was Perplexity-only. resolveScanModel routes platform defaults through OpenRouter's web plugin (:online suffix; Perplexity stays search-native), and each response records searchStatus (performed / not_triggered) so a legitimate zero-citation answer is distinguishable from "search never ran". Historical rows have searchStatus NULL = not_triggered. Free-tier worst-case cost rises to ~$0.40 lifetime per org (documented in free-tier-policy).
  • Provider format drift now fails tests, not production data — recorded real OpenRouter wire fixtures (packages/ai/fixtures/: perplexity 15 citations, :online 5 citations, ungrounded 0, error 400) and a replay suite that drives runPlatformScan through the REAL @openrouter/ai-sdk-provider mapping via a fetch seam on createOpenRouterClient. Pins annotations→sources, ranks, usage, searchStatus, and re-run conflict semantics.
  • N samples per platform — response.sampleIndex + widened unique index (promptId, platform, sampleIndex), options.samples (1-5, free tier forced 1), sequential durable sample steps in the platform worker, scan_session.samplesPerPlatform. Single-draw answers charted as trends was a HIGH audit finding; N>1 makes variance measurable. Default stays 1 (no UI knob yet).
  • Prompt versioning — query.version bumps on any text edit; scan_session.queryVersion snapshots it at dispatch. Editing a saved query no longer silently rewrites its own trend history (readers can now segment; UI annotation is follow-up).
  • One mention-rate definition — PER-RESPONSE everywhere (docs in insights-snapshot.ts): the brand leaderboard (aggregator + procedure) divided by SESSION count and disagreed with the insights snapshot/SoV timeseries for the same window; get-scan + FirstScanResults counted only the primary mention's sentiment while rollups counted per mention. All aligned; brand-profile "X of Y scans" stat stays session-scoped by design (labeled as such).
  • Denominators exclude pending/running/failed scans — the raw-walk read paths (load-context + leaderboard/top-sources/gap-sources/citation-drift/platform-patterns procedures + topic snapshot) counted sessions of ANY status, so clicking Run Scan visibly deflated every mention rate until responses landed. Shared FINALIZED_SCAN_STATUSES now used by every read path including the rollup reader.
  • Registrable-domain identity — new @repo/utils registrableDomainOf (tldts, eTLD+1) + canonicalizeUrl, replacing five duplicated hostnameOf copies. docs.brand.com/brand.com now tally together, unparseable URLs are skipped instead of tallied as garbage keys, unicode hosts key identically to punycode. Policy written down in packages/utils/lib/domain.ts.

Scan engine hardening

  • Citation rank preserved — source.rank (0-based provider order); reads ORDER BY rank. Batch inserts share a second-granularity createdAt, so order was unrecoverable before.
  • Re-runs can no longer mismatch text and children — on insert conflict the engine now extracts against the STORED text and recovers the stored generation's citations from its rawResponse digest (previously: attempt-1 text with attempt-2 mentions/spans/citations). Child delete+reinsert is one transaction (a crash between them used to leave a response with no children and no error).
  • Timeouts — AbortSignal.timeout(90s) on scan calls, 60s on the extractor; a hung upstream can no longer outlive the finalize backstop.
  • Global position ordinal — the regex fallback numbered each brand's mentions from 1 (every brand "best position 1" on the free tier); now a cross-brand order-of-appearance ordinal matching the LLM extractor's semantics.
  • Scheduled scans honor `scan_schedule.platforms` — the column was written by settings but never read at dispatch; every scheduled scan silently ran all four platforms.
  • Unicode word boundaries — brand names ending in accented/non-Latin characters ("Café") never matched under ASCII \b; lookaround boundaries with the u flag fix it. Exclusion-phrase path now has tests.
  • `PLATFORM_ORDER` exported from `@repo/ai` — replaces seven private copies across dashboard/pages.

Wave 2 (same day, [plan 042](.apsolut/03-plan/DONE/042-plan-citation-engine-wave-2.md)) — reliability + forensics

  • Late responses can no longer be stranded (F8) — rollup idempotency moved from a session-level seal to per-response markers (analytics_daily_applied_response, backfilled for sealed history). A response landing after the 3-minute backstop or a reap now folds into analytics_daily on re-apply; the platform worker re-applies after late landings (promoting reaper-failed sessions with data to partial), and the reaper itself finalizes with real semantics (partial + rollup + revalidate) instead of a bare status flip. reset:rollup/backfill:rollup updated for the new marker.
  • Durable retries (F5) — runPlatformScan rethrows retryable failures (SDK isRetryable, RetryError-wrapped exhaustion, abort timeouts) so Inngest retries with backoff (retries: 3); terminal errors and final attempts fall through to quarantine.
  • Failed calls leave evidence (F6/F17) — new scan_error table (platform, sampleIndex, errorClass rate_limit|timeout|schema|provider|unknown, message, raw body). rawResponse on successes now also carries the verbatim provider wire body for future re-parsing.
  • Extractor can't silently zero out mentions (F9) — span repair (indexOf when the quote is unique) before the offset guard, and the engine falls back to regex candidates when the LLM answered but every mention was guard-dropped. Guard + repair now unit-tested.
  • Run manifest (F14/D3/F20) — scan_session.config (platforms, samples, temperature, maxTokens, systemPrompt, resolved model ids, locale) at dispatch; scan_session.summary (expected/succeeded/failedPlatforms/totalCostMicros) at finalize. Explicit DEFAULT_SCAN_TEMPERATURE = 0.7 replaces per-provider defaults.
  • "Your site was cited" (C1, write side) — analytics_daily.citedScans counts responses whose citation set contains the brand's website registrable domain; cited-but-unmentioned competitors materialize zero-scan cells no reader divides by. Read/UI surfacing is plan 042 §12.
  • Citation dedupe (F16) — identical canonical URLs within one response count once (write-side).
  • Matcher versioning (C5) — brand_mention.matcherVersion stamped from MATCHER_VERSION = 2 (unicode-boundary era); backfill script stamps too.
  • Monthly cost alert (F19) — cost_threshold rule: MTD spend vs COST_ALERT_MONTHLY_USD (default $50), once per calendar month per org, new ALERT_COST_THRESHOLD notification type wired through catalog + preferences + i18n.
  • Global scan concurrency cap (F23) — org-level 10 plus a global 30 on the platform worker.
  • CI hygiene — turbo test task declares outputs: [] (warnings gone, caching on).

Wave 3 (same day) — the deferred list, cleared

  • "Your site cited" surfaced (C1 read) — InsightSnapshot.current/previous.cited on both read paths (raw walk + rollup reader, reconciliation-tested non-zero) with a new KPI tile ("N/M answers") on Insights.
  • Inline `[n]` markers are links (B5) — linkifyCitationMarkers turns Perplexity-style markers into links to the ranked citation ([1] → rank 0, off-by-one pinned by tests); get-scan sources now ORDER BY rank.
  • Samples knob — RunScanForm select (1/2/3/5) wired to options.samples; server still clamps and forces 1 on free tier.
  • Prompt-version markers (D2 read) — the query trend chart draws "prompt v{n}" reference lines at each version's first run, so rewording breaks are attributable.
  • Dated model pin — openai/gpt-4o-mini-2024-07-18 (verified in OpenRouter's catalog + a live :online call with 5 citations); Anthropic/Gemini/Perplexity have no dated slugs on OpenRouter (documented; response.model remains the mitigation). Extractor pinned too.
  • Provider upgraded to `@openrouter/ai-sdk-provider@2.10.0` (spec v3 — kills the AI SDK compat-mode warning); validated by the fixture-replay suite on first run, which is exactly what it was built for.
  • Extractor bills the org's BYOK key — extraction spend previously landed on the shared operator key for BYOK orgs; now it rides the same client as the scan (env-key fallback kept for the backfill script).
  • Prod flip checklist — DEPLOY.md §7b (migrate → backfill → compare → flag → epoch note → cost sanity). Webhook error-body reads capped at 4KB.

Still open

    Page 2 of 8