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.

August 2, 2026

Closed beta: invite-only signup, per-member spend cap, dependency sweep

  • Signup is closed (enableSignup: false) for the closed-beta window. Two flags move together, and this is the important part: invitationOnlyPlugin only matches /sign-up/email, while Better Auth's magic link auto-creates an unknown user with `emailVerified: true` unless disableSignUp is set. magicLink({ disableSignUp: !config.enableSignup }) now tracks the config flag — without it the login form's magic-link field would keep minting accounts for any address while public signup _looked_ closed. Reopening signup means flipping both back together. Not a regression: with signup closed, autoSignIn turns on and requireEmailVerification off by design — the invitation proves the address.
  • Per-member scan cap (PER_MEMBER_SCAN_CAP, apiKeys/lib/member-scan-cap.ts). Members cannot add or edit org API keys (every write requires org admin), so they spend the admin's key — and the free-tier gates only run when usedOurKey is true, so under BYOK there was no per-member limit at all and budgetCapUsd defaults to NULL. The cap therefore keys off the member, not the key source, and applies on both. Gate order: free-tier (managed only) → member cap → budget cap, so a capped member never burns a monthly quota slot. No-ops for cron runs (userId null) and for org owners/admins. No migration — scan_session already carries userId.
  • Fixed: bulk-execute re-ran its per-unit preflight only when usedOurKey, so a single BYOK bulk run could blow straight past the member cap in one batch. It now always re-runs.
  • All 10 open Dependabot advisories resolved (1 high, 9 moderate). Every one was the same package on the same path — start-server-and-test > wait-on > axios@1.16.0 — with the same fix. Pinned axios: "^1.18.0" in the pnpm-workspace.yaml overrides block (resolves to 1.19.0). Dev-only e2e orchestration; nothing shipped was exposed, which is why pnpm audit --prod stayed green the whole time. No code imports axios directly.
  • Docs: VERCEL.md now covers the launch-day traps it was missing — Hobby _fails the deployment_ on sub-daily crons (it does not silently skip them), production must start with an empty Turso DB (a restored local.db leaves BYOK rows undecryptable under the new KEYRING_MASTER_KEY and forces a rollup backfill), the expected check:go-live:prod result, and a new §7b for first login, since with signup closed there is no way into a fresh deployment without create:user + grant:tier.

August 2, 2026

Alert volume: repeats must earn themselves, discoveries can be resolved, email batches

  • The discovery alert could never stop. ALERT_NEW_COMPETITOR_DISCOVERED re-fired every 7 days over a 14-day rolling window for the same untracked name, forever. Its two exclusion filters (observation.promotedBrandId, observation.dismissedAt) were never written by any code path — and the "Track" button called brands.create, which creates a brand row but stamps nothing, so tracking a competitor didn't silence the alert about it either. The email told users to do the one thing that wouldn't work.
  • Materiality gate on every repeat (claimAlertFire). A cooldown is a timer, and a timer re-sends alerts whose situation hasn't changed. A repeat now also has to clear a per-rule bar against the last-reported value: counts must grow ≥1.5×, mention rate must move ≥5pp in either direction (a drop → recovery → fresh drop is real news even when the new number isn't below the old one). Outage-class alerts (API key, spend threshold) are exempt by design. A suppressed repeat deliberately does not reset lastFiredAt, so a genuine move tomorrow isn't held for another cooldown.
  • Discovered competitors now have an end state — plan 014's Phase 2, never built. New tracking.discoveries procedures (list / promote / dismiss / restore) and a card on /brands; the alert deep-links the row (/brands?candidate=<name>). Promote creates the brand and stamps every matching observation in one transaction, and is idempotent against a name that's already tracked. Dismiss writes a discovery_dismissal row keyed (organizationId, canonicalName) — a per-row marker can't work, because tomorrow's scan inserts fresh observation rows with a null marker that re-cross the threshold within days.
  • Daily email digest. Market-movement alerts (competitor surge, new competitor, mention rate drop) default to one batched email at 12:00 UTC; act-now alerts (API key, spend threshold) and the weekly digest still send immediately. Implemented as an outbox collapse, not sender-side grouping: parked rows carry digestGroup and are excluded from the drain query _and_ the row claim, so a new composer cron owns them completely and folds N rows into one ordinary outbox row. Every row-scoped invariant (atomic claim, lease, idempotencyKey, backoff) is untouched. A lone parked row is released as a normal single email rather than a one-item digest.
  • Nothing batched or suppressed is lost: the in-app notification list still receives every alert the moment it fires, whatever the email settings say. Per-type email cadence (Immediate / Daily / Off) is in Settings → Notifications; in-app stays a simple on/off because a feed has no cadence.
  • Migration 0046: discovery_dismissal, user_notification_preference.frequency (defaults OFF, which preserves the old "a row means disabled" semantics for every existing row), notification_delivery_outbox.digestGroup. New cron /api/cron/compose-notification-digest.
  • Copy: the discovery alert no longer prints a raw URL path in prose ("Promote X from /brands") and now names dismissal as the other way out. 22 new tests (439 total).

July 28, 2026

Scan-dispatch stability batch

  • Credit-exhaustion circuit breaker. After 3+ "insufficient credits"-class provider errors in 15 minutes with no success since, bulk and scheduled dispatch pause that platform for the org instead of firing hundreds of scans into a dead key (the exact incident behind 1,200+ recorded errors locally). Manual single scans deliberately bypass it — a successful manual run is the probe that resumes automatic dispatch instantly. Derived from existing error/response rows: no migration, no pause state to corrupt.
  • In-flight guard on bulk. Re-running "failed" queries skips any whose retry is already pending/running (double-click / two admins). Toast reports skips.
  • Job-based bulk ("Run all N"). With a status filter active, one click launches every matching query as a background Inngest job — server resolves the selector, fans out in durable 25-query chunks (survives restarts/redeploys mid-job, resumes from the last chunk), re-checks in-flight + credit breaker per chunk, ceiling 1000/job. The manual ≤25 selection flow is unchanged. Inngest function count: 8 → 9 (bulk-scan-fanout).

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.

Page 3 of 9