Drop the .toLowerCase() call when building the cache key's targetValue so
it matches whatever detectTarget returns. detectTarget already normalizes
domains to lowercase; keywords are passed through as-is, so lowercasing
again folded otherwise-distinct keyword queries into the same cache
entry.
The callers of startAudit and beginRankCheckRun were forwarding the
incoming billingCustomer object directly into workflow.create params.
At runtime the object can be an EnsuredUserContext with extra fields
beyond BillingCustomerContext, which pollutes the workflow instance's
persisted params payload. Explicitly pick the four required fields so
only the intended data is serialized into the workflow.
INTERNAL_ERROR was showing "An unexpected error occurred" to users and
triggering PostHog error monitoring for expected validation cases
(duplicate domain+country, max configs reached).
Replace DOMAIN_REGEX with URL-based normalization so users can paste
full URLs (e.g. https://example.com/path) into the domain config form
without getting a ZodError. The URL class handles protocol stripping,
hostname extraction, and validation natively.
* feat: group navigation into Keywords, Domain, and AI dropdowns
Reorganize the top nav from 7 flat items into grouped dropdowns:
- Keywords: Keyword Research, Saved Keywords, Rank Tracking
- Domain: Domain Overview, Backlinks, Site Audit
- AI: standalone link
Mobile sidebar uses section headers for the same groupings.
Active dropdown items are highlighted with primary tint.
* fix: use matchSegment lookup instead of hardcoded array indices in nav groups
* landing: convert main CTA to sign-up, add shared marketing layout
- Replace email waitlist form with "Try now" sign-up button as primary CTA
- Add shared _marketing layout route with consistent nav (Pricing, GitHub, Sign in)
- Move email form to bottom of homepage as "Stay in the loop" newsletter
- Update self-host card copy for live managed version
- Reorder self-host card below video on mobile via flex order
- Add /pricing to sitemap and SiteFooter
* change footer and add social card
* landing: move newsletter and footer to shared marketing layout
Move divider line above the newsletter form instead of between
the form and footer links.
The full middleware context object passed as billingCustomer contained
non-serializable properties (Drizzle project result, TanStack internals)
that caused a DataCloneError in Miniflare's workflow binding during
Docker self-hosting.
* fix: default backlinks search to site-wide scope for root URLs
URLs with an explicit protocol but no path (e.g. https://example.com/)
were incorrectly auto-selecting "Exact page" scope. Now only URLs with
an actual path beyond "/" trigger page scope auto-selection.
* test: update backlinks scope tests for root URL behavior change
* feat: add structured filters to backlinks tables
Add per-tab filter panels (Backlinks, Referring Domains, Top Pages) with
include/exclude text search, numeric range filters, link type selector,
and visibility toggles. Filter values persist in localStorage across
searches and page reloads.
* fix: improve backlinks filter layout and hide domain overview filters on pages tab
- Add w-full to text filter inputs so they fill their grid columns
- Restructure backlinks results card to use border-separated sections
matching the domain overview pattern
- Hide filter button and panel on domain overview's Top Pages tab since
keyword filters don't apply there
* fix: move backlinks filter button to its own toolbar row below tabs
Match the domain overview layout: tabs in their own header row, then a
separate toolbar row with the Filters button underneath.
* style: fix prettier formatting in backlinks filter files
* fix: resolve all ci:check issues
- Remove unused BacklinksResultsHeader.tsx and useBacklinksSpamPreferences.ts
- Un-export DEFAULT_BACKLINKS_SPAM_THRESHOLD and normalizeBacklinksSpamThreshold
(only used internally)
- Remove unused BacklinksAllFilterValues type
- Fix oxlint unsafe type assertions in useBacklinksFilters.ts
* fix: wire spam filter options through to server and remove spam filters from non-backlinks tabs
The client was not passing hideSpam/spamThreshold to the server, so the
server always defaulted to hideSpam:true with threshold 40. This made
the new client-side spam score filters on the backlinks tab silently
filter an already-truncated dataset. Now the client sends hideSpam:false
so the server returns all rows and client-side filtering works correctly.
Removed spam score filters from the Referring Domains tab since spam
filtering is not needed there.
* fix: restore backlinks spam filtering and safe filter hydration
* simplify: remove server-side spam filter wiring, keep client-side only
All backlinks filtering (including spam score) is now done client-side.
Server-side spam filtering adds complexity without meaningful benefit
at current data volumes. This removes the bridging code added in the
last two commits and simplifies filter hydration.
* feat: add top-level persistent filters for keyword research
Add a collapsible filter bar below the search bar so users can set
default filters (volume, CPC, difficulty, include/exclude terms) that
persist across searches and sessions via localStorage.
* simplify: remove top-level filter UI, keep only localStorage persistence
Revert the search bar to its original state. The existing in-table
filters already provide good UX — all we needed was persistence.
Auto-expand the filter panel when persisted filters are active on load.
* feat: show filtered keyword count as "Showing X of Y keywords"
When filters are active, display "Showing 14 of 150 keywords" instead
of just "14 keywords". When no filters are active, show "Showing X
keywords". Applied to both desktop and mobile views.
* style: fix prettier formatting
* feat: add backlink search history
Save recent backlink searches so users can quickly rerun previous lookups instead of starting from an empty state each time. Also widen the keyword research empty state layout to better use the available space.
* update dev tools
* update recent search navigation across SEO pages
* refine search layouts and recent search navigation
* simplify recent search resets and history storage
* save
* fix recent search hydration mismatch
* fix ci lint issues
* feat: group backlinks table by domain with expandable rows
Group individual backlinks by source domain in the Backlinks tab,
showing a compact domain summary row that expands to reveal individual
backlink entries. Uses TanStack Table for expand/collapse state
management with table-fixed layout for stable column widths.
* fix: ci:check fixes for grouped backlinks table
Extract BacklinkChildRow to its own file to stay under max-lines lint
rule. Remove unused export of sortBacklinkRows. Format files with
prettier.
* refactor: simplify grouped backlinks table
* refactor: split backlinks page tables into focused files
Break the backlinks, referring domains, and top pages tables into dedicated components so the feature stays easier to navigate and no longer needs a max-lines ignore.
* fix: correct grouped backlink totals and remove target sorting
* fix: stabilize backlinks table column layout
Keep the DA header aligned with numeric cells and lock the backlinks table to fixed column widths so expanded rows with long anchor text do not shift the layout.
* refactor: inline backlinks table imports