229 Commits

Author SHA1 Message Date
Ben Senescu
2b7a3eae05
Make domain overview URL paths clickable in tables (#120) 2026-04-16 10:30:49 -04:00
Ben Senescu
e78ef5e3dd
fix: accept full URLs in rank tracking domain input (#117)
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.
2026-04-16 02:58:34 -04:00
Ben Senescu
86d3714846
feat: allow free plan users to set up rank tracking (#116) 2026-04-16 02:19:23 -04:00
Ben Senescu
aeafa5649d
feat: polish rank tracking UI (#115) 2026-04-16 00:42:10 -04:00
Ben Senescu
4595080356
Redesign AI page (#114) 2026-04-15 11:41:04 -04:00
Ben Senescu
1b6bf8fd94
refactor: convert rank tracking table to Tanstack Table (#111) 2026-04-15 10:42:28 -04:00
Ben Senescu
0024691da8 Improve rank tracking empty state and add config route (#112) 2026-04-14 20:29:43 -04:00
Ben Senescu
e68ced7203 feat: group navigation into dropdown menus (#110)
* 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
2026-04-14 20:29:43 -04:00
Ben Senescu
53a83996ef feat: Rank Tracking MVP (#91) 2026-04-14 20:29:38 -04:00
Ben Senescu
fb1291537c
fix: treat empty DataForSEO backlinks results as valid empty data (#109) 2026-04-09 16:44:34 -04:00
Ben Senescu
53505c0a5b
fix: fix runtime error when clearing backlinks filter (#105) 2026-04-09 16:43:47 -04:00
Ben Senescu
4137b386ab
website: convert CTA to sign-up (#108)
* 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.
2026-04-09 15:13:23 -04:00
Ben Senescu
937665ef95
fix: remove unused view transitions CSS causing navigation errors (#107) 2026-04-09 14:21:47 -04:00
Ben Senescu
1d9a503335
hosted: add free trial plan and onboarding (#104) 2026-04-08 19:06:35 -04:00
Ben Senescu
01c11b0c5f
release: v0.0.5 (#102) 2026-04-08 14:18:46 -04:00
Ben Senescu
0b7811d882
fix: sanitize CSV exports against formula injection (#103) 2026-04-08 14:18:27 -04:00
Ben Senescu
c9d212b8b8 fix: explicitly pick billingCustomer fields for workflow serialization (#97)
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.
2026-04-08 14:09:02 -04:00
Ben Senescu
fd3d9838fe fix: default backlinks search to site-wide scope for root URLs (#96)
* 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
2026-04-08 14:09:02 -04:00
Ben Senescu
93c95dc8f7 feat: add structured filters to backlinks tables (#94)
* 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.
2026-04-08 14:09:02 -04:00
Ben Senescu
b3d543d206 feat: add top-level persistent filters for keyword research (#93)
* 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
2026-04-08 14:09:02 -04:00
Ben Senescu
f7ccdfee38 feat: add backlink search history (#90)
* 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
2026-04-08 14:09:02 -04:00
Ben Senescu
868d83fb1c Add per-tab CSV export for backlinks results (#95)
* Add CSV export per backlinks tab

* save
2026-04-08 14:09:02 -04:00
Ben Senescu
d2fc85342a hosted: track successful subscription checkout (#92) 2026-04-08 14:09:02 -04:00
Ben Senescu
a10f4d82a0 feat: hide spam backlinks by default (#89)
* feat: hide spam backlinks by default

* feat: add configurable backlinks spam filtering

* refactor: simplify backlinks spam filtering flow

Reduce duplicated spam-filter state and normalization so the backlinks page has one source of truth and client/server caching stays aligned.

* refactor: simplify backlinks spam filter semantics

* fix: keep spam filtering scoped to backlink rows

* feat: show backlinks spam score
2026-04-08 14:09:02 -04:00
Ben Senescu
673459877e feat: group backlinks table by domain with expandable rows (#88)
* 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
2026-04-08 14:09:02 -04:00
Ben Senescu
6f307b9cf6 fix: fix saving keyword bugs (#86)
* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move DataForSEO response cache from KV to R2 (#46)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move DataForSEO response cache from KV to R2

KV TTL-based expiry is imprecise for cache freshness. Switch to R2 with
soft TTL via custom metadata (expiresAt) and a 7-day lifecycle rule for
cleanup. Cache objects live under the `dataforseo-cache/` prefix,
separate from durable audit payloads in `site-audit/`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: use Workers crypto for R2 cache keys

Keep the cache helper aligned with the Cloudflare runtime and avoid pulling Worker bindings into Vitest. Also clarify that R2 lifecycle cleanup is optional but recommended to control storage growth.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks (#48)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks

* fix: satisfy ci checks for hosted billing

* refactor: simplify hosted Autumn billing integration

Limit Autumn wiring to the billing route and remove billing-specific indirection so the hosted billing flow is easier to reason about. Stop sending organization IDs as fake customer names to keep customer identity data honest.

* fix: satisfy ci line-limit check for hosted billing

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add PostHog error tracking for client and server

Integrate posthog-js and posthog-node to capture unhandled errors in
both the client error boundary and server function middleware, skipping
expected errors like auth and 404s.

* fix: improve PostHog error tracking setup

- Fix captureExceptionImmediate args (pass undefined as distinctId, not request path)
- Rename VITE_POSTHOG_KEY/POSTHOG_KEY to POSTHOG_PUBLIC_KEY (single env var for both client and server)
- Expose POSTHOG_PUBLIC_KEY and POSTHOG_HOST via Vite envPrefix instead of VITE_ prefix
- Enable capture_exceptions in client posthog-js init for error tracking
- Use waitUntil() instead of await for server error capture (non-blocking)
- Add try/catch around client captureException to prevent unhandled rejections

* fix: align PostHog setup with lint rules

* fix: gate PostHog to hosted mode

* docs: remove hosted PostHog README note

* fix: improve social previews and shared icons (#52)

Add share metadata and a real product social card so OpenSEO links render with a stronger preview. Sync the landing site and app shell favicons with the shared Every App icon set.

* refactor: use TanStack Form everywhere (#49)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.


* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------


* refactor: unify app forms with TanStack Form

Replace bespoke field and submit error state so validation, async submit handling, and router-synced inputs behave consistently across the app.

* fix: remove dead code from ci check

* fix: delay required form errors until input interaction

* fix: preserve detailed DataForSEO error messages

* fix: sanitize internal errors and delay domain validation

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Bump to v0.0.4

* Add pricing page (#58)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move DataForSEO response cache from KV to R2 (#46)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move DataForSEO response cache from KV to R2

KV TTL-based expiry is imprecise for cache freshness. Switch to R2 with
soft TTL via custom metadata (expiresAt) and a 7-day lifecycle rule for
cleanup. Cache objects live under the `dataforseo-cache/` prefix,
separate from durable audit payloads in `site-audit/`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: use Workers crypto for R2 cache keys

Keep the cache helper aligned with the Cloudflare runtime and avoid pulling Worker bindings into Vitest. Also clarify that R2 lifecycle cleanup is optional but recommended to control storage growth.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks (#48)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks

* fix: satisfy ci checks for hosted billing

* refactor: simplify hosted Autumn billing integration

Limit Autumn wiring to the billing route and remove billing-specific indirection so the hosted billing flow is easier to reason about. Stop sending organization IDs as fake customer names to keep customer identity data honest.

* fix: satisfy ci line-limit check for hosted billing

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add pricing page

* merge

* Revert home page back to normal

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Separate billing buckets (#57)

* refactor: move DataForSEO response cache from KV to R2 (#46)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move DataForSEO response cache from KV to R2

KV TTL-based expiry is imprecise for cache freshness. Switch to R2 with
soft TTL via custom metadata (expiresAt) and a 7-day lifecycle rule for
cleanup. Cache objects live under the `dataforseo-cache/` prefix,
separate from durable audit payloads in `site-audit/`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: use Workers crypto for R2 cache keys

Keep the cache helper aligned with the Cloudflare runtime and avoid pulling Worker bindings into Vitest. Also clarify that R2 lifecycle cleanup is optional but recommended to control storage growth.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks (#48)

* refactor: move lighthouse audits to dataforseo (#43)

* refactor: move lighthouse audits to dataforseo

* chore: remove obsolete audit settings modal

* refactor: rename psi flows to lighthouse

* save

* refactor: simplify audit lighthouse storage flow

* fix: separate lighthouse metrics from actionable audits

* refactor: remove redundant audit project inputs

* feat: redesign lighthouse issues screen with score gauges and table layout

Replace flat score cards with circular SVG gauges, condense metrics into
a compact grid, and switch issue list from cards to an expandable table
with fixed column widths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: harden lighthouse regression coverage

* fix: restore project-scoped audit inputs

* refactor: simplify lighthouse payload handling

* refactor: inline lighthouse server handlers

* refactor: share audit workflow types

* refactor: simplify lighthouse payload flows

* save

* refactor: drop project pagespeed api key

* fix: restore lighthouse issues loading with resilient project context

* fix: restore audit issues back navigation

* refactor: simplify project context and lighthouse error handling

* fix: tolerate DataForSEO lighthouse payload drift

* refactor: route audit lighthouse through dataforseo client

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: move hosted billing to Autumn hooks

* fix: satisfy ci checks for hosted billing

* refactor: simplify hosted Autumn billing integration

Limit Autumn wiring to the billing route and remove billing-specific indirection so the hosted billing flow is easier to reason about. Stop sending organization IDs as fake customer names to keep customer identity data honest.

* fix: satisfy ci line-limit check for hosted billing

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: split SEO credits into monthly included and top-up pools

Replaces the single usage_credits balance with two separate pools:
- usage_credits: monthly grant from base-plan, resets each cycle
- topup_credits: one-off purchases from credit-top-up, never expire

Usage now deducts from monthly credits first, then falls back to
top-ups. The billing UI shows both balances separately with a reset
date on the monthly card. Requires Autumn dashboard changes to create
the topup_credits credit system and update the credit-top-up plan.

* formt

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Apply button to keyword research filters (#59)

* feat: add Apply button to keyword research filters

Filters no longer live-update on every keystroke. Users can now set
multiple filter fields and click "Apply" to apply them all at once.

* fix: subscribe to form store so filter Apply button is reactive

useStore(filtersForm.store) ensures re-renders on field changes,
fixing the disabled Apply button when filter inputs have values.

* fix: use useStore for reactive live-filtering in keyword filters

Revert the Apply button approach in favor of the original live-filter
behavior. The actual issue was that filtersForm.state.values didn't
trigger re-renders on field changes. Using useStore(filtersForm.store)
properly subscribes to the form store so filters apply as the user types.

* redesign: auth pages (#60)

Remove card/shadow layout in favor of a clean centered design.
Add tree logo, simplify inputs (placeholder-only, no labels),
use soft buttons, narrow form width, and clean up footer links.

* fix: remove unused AUTUMN_SEO_DATA_USAGE_FEATURE_ID export

This constant became unused after the billing buckets separation
in #57 which replaced it with separate balance/topup feature IDs.

* fix: resolve oxlint errors from billing buckets PR

Add eslint-disable for max-lines in HostedBillingContent.tsx.
Type trackMock properly in dataforseoClient.test.ts to eliminate
unsafe type assertions without needing eslint-disable.

* Refactor billing + onboarding (#61)

* fix: use full page reload after email verification

Client-side navigation via TanStack Router during the auth→app
transition can race with Vite HMR, causing "action is not a function"
server function errors.

* feat: add minimal /subscribe onboarding page

New post-auth subscribe page at /subscribe using the same centered
layout as auth pages. Shows plan details and a single Subscribe CTA.
Redirects PAYMENT_REQUIRED users here instead of /billing.

* redesign: rewrite billing page with usage chart and cleaner layout

Delete the sprawling multi-component billing page and replace it with a
single-file implementation. Two cards sit side by side at the top
(subscription summary + buy credits), with a 30-day usage bar chart
below powered by Autumn's useAggregateEvents hook and recharts.

Removed BillingRouteParts.tsx, HostedBillingContent.tsx, and trimmed
HostedBillingContentUtils to only parseTopUpAmount.

* polish: billing page improvements and OpenSEO nav link

- Two-column layout with subscription summary and buy credits side by side
- Usage bar chart using ResizeObserver instead of ResponsiveContainer
- Input validation with inline error message
- Full-page redirect state when navigating to Stripe
- Make OpenSEO logo in navbar link to /

* fix: guard app routes and include top-up usage

* fix: restore billing onboarding guards

Keep unpaid orgs on /subscribe and avoid misleading billing states when Autumn customer lookups fail.

* fix: split billing usage chart for ci checks

* fix: match auth page background color to body (#62)

AuthPageShell used bg-base-100 while the body uses bg-base-200,
causing two different shades of black on login/sign-up pages.

* website: terms + privacy policy (#56)

* feat: add markdown-backed legal pages

Move privacy into the Fumadocs content pipeline and add a terms page so legal copy can be managed as markdown alongside other generated pages.

* save terms draft

* clean up notes

* Add privacy policy

* Update policies

* save

* simplify legal page routes

* fix: match auth page background color to body (#64)

AuthPageShell used bg-base-100 while the body uses bg-base-200,
causing two different shades of black on login/sign-up pages.

* chore: add hosted Cloudflare deploy config (#66)

Keep app.openseo.so routes out of the default Wrangler config so the public Deploy to Cloudflare button continues to work.

* fix:  remove better auth background tasks (#67)

* Improve verify email page copy and design

Remove redundant green success alert, update title to "Verify your email",
rename button to "Resend email", and change footer link to "Back to sign in".

* Fix verification emails not sending in production

Remove the backgroundTasks/waitUntil handler so better-auth awaits
email sending inline. Previously, errors thrown inside waitUntil
promises were silently dropped by Cloudflare Workers, preventing
emails from sending with no visible error logs.

* fix: go back to orignal wrangler.jsonc

* feat: add sign out button to /subscribe

* Fix project-route auth gaps for domain and SERP lookups (#65)

* fix: tweak email verification copy (#68)

* Improve verify email page copy and design

Remove redundant green success alert, update title to "Verify your email",
rename button to "Resend email", and change footer link to "Back to sign in".

* Fix verification emails not sending in production

Remove the backgroundTasks/waitUntil handler so better-auth awaits
email sending inline. Previously, errors thrown inside waitUntil
promises were silently dropped by Cloudflare Workers, preventing
emails from sending with no visible error logs.

* fix: set default app page title (#72)

* feat: add theme switcher with system/light/dark segmented control (#75)

* feat: add theme switcher with system/light/dark segmented control

Add a three-way theme toggle (system, light, dark) as a segmented
icon control in the account dropdown menu. Rename placeholder daisyUI
theme names from "todo" to "openseo".

* refactor: always resolve data-theme explicitly, remove duplicated dark CSS

Instead of removing data-theme in "system" mode and relying on
@media (prefers-color-scheme: dark) CSS, always resolve to an
explicit theme name via matchMedia. This eliminates the duplicated
dark-mode CSS overrides.

* fix: fix theme switcher cursor

* feat: add filtering and sorting to domain overview (#74)

* feat: add filtering and sorting to domain overview

Add numeric range filters (traffic, volume, CPC, KD/score, rank) and
text include/exclude filters to the domain overview page. Make CPC and
Score columns sortable. Filters persist across searches and auto-apply
to new results.

* fix: match domain filter panel layout to keyword research pattern

Restructure DomainResultsCard to use border-separated sections instead
of card-body gap layout. Filters button now sits in a toolbar row with
keyword count and search, matching the keyword research page's design.

* fix: make filter text inputs full-width so labels stack above

* fix: prettier formatting

* fix: extract domain filtering logic to fix lint errors

Move filterAndSortKeywords into domainFiltering.ts to bring
controllerInternals under 350 lines. Use explicit key array
in useDomainFilters to avoid unsafe type assertion.

* feat: add optional helpText tooltip to SortableHeader

Reuses HeaderHelpLabel from keywords components to show hover
tooltips on CPC and Score column headers.

* save

* fix: move theme switcher to bottom of account menu (#78)

* fix: add legal links to hosted sign-up footer (#77)

* feat: color-code rank badges in backlinks tables (#73)

* feat: color-code rank badges in backlinks tables

Add colored score badges to rank/authority columns across all backlinks
tables (Backlinks, Referring Domains, Top Pages). Uses the same tier
system as keyword difficulty but reversed (higherIsBetter) so high
authority shows green and low authority shows red.

* style: fix prettier formatting in BacklinksPageTables

* style: fix prettier formatting in subscribe page

* fix: use backlinks history for default trends (#69)

* fix: use backlinks history for default trends

* simplify backlinks: remove filters, always use history endpoint

Remove the filter UI (status, subdomains, indirect links, exclude internal)
and hardcode defaults across the stack. Replace the conditional
timeseries_summary + timeseries_new_lost_summary fallback with a single
backlinks/history/live call for trend data. This reduces the overview from
5 parallel API calls to 3 and removes all conditional branching.

* improve charts

* fix: refresh backlinks cost docs

* fix: stop reporting handled payment required errors (#70)

* fix: stop reporting handled payment required errors

* save

* fix: lock lighthouse launch flow to auto sampling (#80)

* fix: lock lighthouse launch flow to auto sampling

* save

* fix ci (#79)

* fix: handle empty keyword research results (#81)

Treat successful DataForSEO keyword responses with null items as empty results so auto mode can fall back cleanly. Simplify the no-results state by removing dead-end actions and top-aligning the empty card.

* Revert "feat: color-code rank badges in backlinks tables (#73)"

This reverts commit 7334a83cbd5aa367bbbaa406671eb74589bf73b8.

* feat: add "Get Started" CTA to pricing page managed section (#82)

* fix: handle empty DataForSEO task results (#84)

* fix: accept empty DataForSEO task results

Treat successful tasks with null items as empty payloads so empty ranked keyword responses do not fail billing validation.

* refactor: simplify DataForSEO null result handling

Add .nullable() to the existing structured result schema instead of
loosening the type to unknown[] and re-parsing in parseTaskItems.

* refactor: simplify backlinks zod parsing with structured result schema

Same pattern as the dataforseoSchemas fix: give taskSchema.result a
structured type with .items instead of z.unknown(), removing the
intermediate resultItemsSchema and two-step parsing in parseItems.

* refactor: replace manual type guards with zod schemas

- progress-kv.ts: replace isCrawledUrlEntry type guard with a zod
  schema and use jsonCodec(z.array(...)) instead of parsing unknown
  then filtering
- helpers.ts: tighten normalizeIntent param from unknown to
  string | null | undefined to match actual call sites
- dataforseoBacklinksSupport.ts: allow null result elements to match
  API responses where result contains [null]

* refactor: filter null result elements at the source

Filter out null elements from task.result in postBacklinks so
downstream functions receive clean BacklinksTaskResult[] instead
of (BacklinksTaskResult | null)[].

* save

* hosted: add product analytics (#83)

* track core product analytics flows

Track auth, search, export, audit, and credit-consumption events with canonical route IDs so PostHog funnels and usage dashboards stay low-noise and privacy-safe.

* fix: keep auth actions usable after session loss

* refactor: simplify analytics and auth helpers

- Replace isRecord/getActiveOrganizationId type guards with simple cast
- Refactor getAnalyticsRouteContext from if/return chain to route tables
- Replace toVerificationIssueType switch with zod enum
- Merge duplicate credits_consume events into single event per API call
- Merge two PostHogBootstrap useEffects into one

* refactor: add projectId to middleware context to reduce boilerplate

The requireProjectContext middleware now includes projectId directly,
eliminating repeated manual construction of BillingCustomerContext
objects across all server function handlers.

* remove unused BILLING_* env var fallbacks from cost profile script

* remove before_send event enrichment to preserve native PostHog URL tracking

The before_send hook was stripping $pathname, $current_url, $referrer and
other URL properties, which breaks PostHog web analytics dashboards, paths
analysis, session replay, and attribution. The route_id/route_group injection
it provided is unnecessary since PostHog already captures $pathname natively.

* remove route mapping layer, pass raw redirect paths to analytics events

The route ID registry (STATIC_ROUTES, PROJECT_ROUTES, getAnalyticsRouteContext,
getRedirectRouteId) duplicated what PostHog already captures via $pathname.
Replace redirect_route_id with redirect_to containing the raw path, and remove
~80 lines of route mapping infrastructure.

* clean up analytics events: drop redundant submit events and derived properties

- Remove search_submit events for keywords, domain overview, and backlinks
  (the search_complete events capture the meaningful outcome data)
- Remove target_type from backlinks events (derived 1:1 from search_scope)
- Remove result_limit from keyword research (requested limit, not useful
  alongside actual result_count)
- Remove export_format from data:export events (always "csv")

* refactor: inline wrappers, colocate helpers, deduplicate getActiveOrganizationId

- Inline toVerificationIssueType into verify-email.tsx (single-use wrapper)
- Move mapDataforseoPathToCreditFeature into dataforseoClient.ts (only consumer)
- Extract shared getActiveOrganizationId into lib/auth-session.ts (was
  duplicated in __root.tsx and middleware/ensure-user/hosted.ts)
- Rename shared/analytics.ts → shared/internal-user.ts (only email helpers
  remain after removing route mapping, verification, and dataforseo helpers)

* remove internal user tracking and email domain properties

Drop is_internal_user super property, email_domain person property, and all
supporting code (shared/internal-user.ts, getEmailDomain, isInternalUserEmail).
Simplifies initPostHog and identifyAnalyticsUser signatures.

* remove backlinks:search_complete effect-based tracking

The reactive useEffect + useRef dedup pattern added ~30 lines of plumbing
inside a data hook for a single analytics event. Not worth the complexity.

* simplify: replace manual type guards with zod, deduplicate posthog and sign-out helpers

- Replace hand-rolled typeof checks in getActiveOrganizationId and
  isAuthenticatedServerFunctionContext with zod safeParse
- Extract withPostHogClient helper to deduplicate client posthog wrapper
- Move apiKey guard into getServerPostHogClient factory
- Extract signOutAndRedirect to avoid duplicated sign-out logic
- Drop derivable has_results from analytics events
- Remove unnecessary path normalization in mapDataforseoPathToCreditFeature

* fix: strip email from pageview URLs, restore sign-out guard, harden server posthog, fix path mapper

- Sanitize $current_url on pageviews to remove email query param (PII)
- Restore onSuccess for sign-out redirect to avoid bounce-back on failure
- Swallow shutdown() errors so PostHog outages can't fail billed work
- Rewrite mapDataforseoPathToCreditFeature to match real API path structure
  (path[1] = module, path[3] = endpoint) instead of scanning all segments

* simplify: remove redundant refs in verify-email, infer middleware context type

- Remove unnecessary useRef guards in verify-email effects (deps already prevent re-firing)
- Use z.ZodType<EnsuredUserContext> annotation to infer return type instead of casting
- Add comment explaining one-shot PostHog client on Workers

* fix: reset PostHog identity on sign-out before redirect

* fix: require POSTHOG_HOST env var instead of defaulting to us.i.posthog.com

* fix: annotate url as unknown to satisfy no-unsafe-assignment

* format

* fix: batch saved keyword inserts for D1

* feat: pass keyword metrics when saving to avoid redundant fetches

Send research metrics (volume, CPC, competition, difficulty, intent,
trend) alongside saved keywords so the server can store them directly
instead of re-fetching from DataForSEO. Reuse SaveKeywordsInput type
from the Zod schema instead of duplicating the type inline.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:09:02 -04:00
Ben Senescu
4d94a1a595 feat: add Bangladesh support for keyword research (#87)
Add Bangladesh (BD, location code 2050) with Bengali (bn) language
to the keyword research location options.
2026-04-08 14:09:02 -04:00
Ben Senescu
e5206ec07b fix: batch saved keyword inserts for D1 (#85) 2026-04-08 14:09:02 -04:00
Ben Senescu
ad3b732f60 hosted: add product analytics (#83)
* track core product analytics flows

Track auth, search, export, audit, and credit-consumption events with canonical route IDs so PostHog funnels and usage dashboards stay low-noise and privacy-safe.

* fix: keep auth actions usable after session loss

* refactor: simplify analytics and auth helpers

- Replace isRecord/getActiveOrganizationId type guards with simple cast
- Refactor getAnalyticsRouteContext from if/return chain to route tables
- Replace toVerificationIssueType switch with zod enum
- Merge duplicate credits_consume events into single event per API call
- Merge two PostHogBootstrap useEffects into one

* refactor: add projectId to middleware context to reduce boilerplate

The requireProjectContext middleware now includes projectId directly,
eliminating repeated manual construction of BillingCustomerContext
objects across all server function handlers.

* remove unused BILLING_* env var fallbacks from cost profile script

* remove before_send event enrichment to preserve native PostHog URL tracking

The before_send hook was stripping $pathname, $current_url, $referrer and
other URL properties, which breaks PostHog web analytics dashboards, paths
analysis, session replay, and attribution. The route_id/route_group injection
it provided is unnecessary since PostHog already captures $pathname natively.

* remove route mapping layer, pass raw redirect paths to analytics events

The route ID registry (STATIC_ROUTES, PROJECT_ROUTES, getAnalyticsRouteContext,
getRedirectRouteId) duplicated what PostHog already captures via $pathname.
Replace redirect_route_id with redirect_to containing the raw path, and remove
~80 lines of route mapping infrastructure.

* clean up analytics events: drop redundant submit events and derived properties

- Remove search_submit events for keywords, domain overview, and backlinks
  (the search_complete events capture the meaningful outcome data)
- Remove target_type from backlinks events (derived 1:1 from search_scope)
- Remove result_limit from keyword research (requested limit, not useful
  alongside actual result_count)
- Remove export_format from data:export events (always "csv")

* refactor: inline wrappers, colocate helpers, deduplicate getActiveOrganizationId

- Inline toVerificationIssueType into verify-email.tsx (single-use wrapper)
- Move mapDataforseoPathToCreditFeature into dataforseoClient.ts (only consumer)
- Extract shared getActiveOrganizationId into lib/auth-session.ts (was
  duplicated in __root.tsx and middleware/ensure-user/hosted.ts)
- Rename shared/analytics.ts → shared/internal-user.ts (only email helpers
  remain after removing route mapping, verification, and dataforseo helpers)

* remove internal user tracking and email domain properties

Drop is_internal_user super property, email_domain person property, and all
supporting code (shared/internal-user.ts, getEmailDomain, isInternalUserEmail).
Simplifies initPostHog and identifyAnalyticsUser signatures.

* remove backlinks:search_complete effect-based tracking

The reactive useEffect + useRef dedup pattern added ~30 lines of plumbing
inside a data hook for a single analytics event. Not worth the complexity.

* simplify: replace manual type guards with zod, deduplicate posthog and sign-out helpers

- Replace hand-rolled typeof checks in getActiveOrganizationId and
  isAuthenticatedServerFunctionContext with zod safeParse
- Extract withPostHogClient helper to deduplicate client posthog wrapper
- Move apiKey guard into getServerPostHogClient factory
- Extract signOutAndRedirect to avoid duplicated sign-out logic
- Drop derivable has_results from analytics events
- Remove unnecessary path normalization in mapDataforseoPathToCreditFeature

* fix: strip email from pageview URLs, restore sign-out guard, harden server posthog, fix path mapper

- Sanitize $current_url on pageviews to remove email query param (PII)
- Restore onSuccess for sign-out redirect to avoid bounce-back on failure
- Swallow shutdown() errors so PostHog outages can't fail billed work
- Rewrite mapDataforseoPathToCreditFeature to match real API path structure
  (path[1] = module, path[3] = endpoint) instead of scanning all segments

* simplify: remove redundant refs in verify-email, infer middleware context type

- Remove unnecessary useRef guards in verify-email effects (deps already prevent re-firing)
- Use z.ZodType<EnsuredUserContext> annotation to infer return type instead of casting
- Add comment explaining one-shot PostHog client on Workers

* fix: reset PostHog identity on sign-out before redirect

* fix: require POSTHOG_HOST env var instead of defaulting to us.i.posthog.com

* fix: annotate url as unknown to satisfy no-unsafe-assignment

* format
2026-04-08 14:09:02 -04:00
Ben Senescu
f3ef909d3e fix: handle empty DataForSEO task results (#84)
* fix: accept empty DataForSEO task results

Treat successful tasks with null items as empty payloads so empty ranked keyword responses do not fail billing validation.

* refactor: simplify DataForSEO null result handling

Add .nullable() to the existing structured result schema instead of
loosening the type to unknown[] and re-parsing in parseTaskItems.

* refactor: simplify backlinks zod parsing with structured result schema

Same pattern as the dataforseoSchemas fix: give taskSchema.result a
structured type with .items instead of z.unknown(), removing the
intermediate resultItemsSchema and two-step parsing in parseItems.

* refactor: replace manual type guards with zod schemas

- progress-kv.ts: replace isCrawledUrlEntry type guard with a zod
  schema and use jsonCodec(z.array(...)) instead of parsing unknown
  then filtering
- helpers.ts: tighten normalizeIntent param from unknown to
  string | null | undefined to match actual call sites
- dataforseoBacklinksSupport.ts: allow null result elements to match
  API responses where result contains [null]

* refactor: filter null result elements at the source

Filter out null elements from task.result in postBacklinks so
downstream functions receive clean BacklinksTaskResult[] instead
of (BacklinksTaskResult | null)[].

* save
2026-04-08 14:09:02 -04:00
Ben Senescu
aed666ef3c feat: add "Get Started" CTA to pricing page managed section (#82) 2026-04-08 14:09:02 -04:00
Ben Senescu
26f433caf8 Revert "feat: color-code rank badges in backlinks tables (#73)"
This reverts commit 7334a83cbd5aa367bbbaa406671eb74589bf73b8.
2026-04-08 14:09:02 -04:00
Ben Senescu
c1190550b0 fix: handle empty keyword research results (#81)
Treat successful DataForSEO keyword responses with null items as empty results so auto mode can fall back cleanly. Simplify the no-results state by removing dead-end actions and top-aligning the empty card.
2026-04-08 14:09:02 -04:00
Ben Senescu
5e9ebaf19f fix ci (#79) 2026-04-08 14:09:02 -04:00
Ben Senescu
bbed7c760a fix: lock lighthouse launch flow to auto sampling (#80)
* fix: lock lighthouse launch flow to auto sampling

* save
2026-04-08 14:09:02 -04:00
Ben Senescu
f75a1d4156 fix: stop reporting handled payment required errors (#70)
* fix: stop reporting handled payment required errors

* save
2026-04-08 14:09:02 -04:00
Ben Senescu
d773105e8d fix: use backlinks history for default trends (#69)
* fix: use backlinks history for default trends

* simplify backlinks: remove filters, always use history endpoint

Remove the filter UI (status, subdomains, indirect links, exclude internal)
and hardcode defaults across the stack. Replace the conditional
timeseries_summary + timeseries_new_lost_summary fallback with a single
backlinks/history/live call for trend data. This reduces the overview from
5 parallel API calls to 3 and removes all conditional branching.

* improve charts

* fix: refresh backlinks cost docs
2026-04-08 14:09:02 -04:00
Ben Senescu
fdb2d453e4 feat: color-code rank badges in backlinks tables (#73)
* feat: color-code rank badges in backlinks tables

Add colored score badges to rank/authority columns across all backlinks
tables (Backlinks, Referring Domains, Top Pages). Uses the same tier
system as keyword difficulty but reversed (higherIsBetter) so high
authority shows green and low authority shows red.

* style: fix prettier formatting in BacklinksPageTables

* style: fix prettier formatting in subscribe page
2026-04-08 14:09:02 -04:00
Ben Senescu
8a4e5f429d fix: add legal links to hosted sign-up footer (#77) 2026-04-08 14:09:02 -04:00
Ben Senescu
749f5b6abb fix: move theme switcher to bottom of account menu (#78) 2026-04-08 14:09:02 -04:00
Ben Senescu
180657fe33 feat: add filtering and sorting to domain overview (#74)
* feat: add filtering and sorting to domain overview

Add numeric range filters (traffic, volume, CPC, KD/score, rank) and
text include/exclude filters to the domain overview page. Make CPC and
Score columns sortable. Filters persist across searches and auto-apply
to new results.

* fix: match domain filter panel layout to keyword research pattern

Restructure DomainResultsCard to use border-separated sections instead
of card-body gap layout. Filters button now sits in a toolbar row with
keyword count and search, matching the keyword research page's design.

* fix: make filter text inputs full-width so labels stack above

* fix: prettier formatting

* fix: extract domain filtering logic to fix lint errors

Move filterAndSortKeywords into domainFiltering.ts to bring
controllerInternals under 350 lines. Use explicit key array
in useDomainFilters to avoid unsafe type assertion.

* feat: add optional helpText tooltip to SortableHeader

Reuses HeaderHelpLabel from keywords components to show hover
tooltips on CPC and Score column headers.

* save
2026-04-08 14:09:02 -04:00
Ben Senescu
fc53e9a77c fix: fix theme switcher cursor 2026-04-08 14:09:02 -04:00
Ben Senescu
3081bc624a feat: add theme switcher with system/light/dark segmented control (#75)
* feat: add theme switcher with system/light/dark segmented control

Add a three-way theme toggle (system, light, dark) as a segmented
icon control in the account dropdown menu. Rename placeholder daisyUI
theme names from "todo" to "openseo".

* refactor: always resolve data-theme explicitly, remove duplicated dark CSS

Instead of removing data-theme in "system" mode and relying on
@media (prefers-color-scheme: dark) CSS, always resolve to an
explicit theme name via matchMedia. This eliminates the duplicated
dark-mode CSS overrides.
2026-04-08 14:09:01 -04:00
Ben Senescu
bd10955a8d fix: set default app page title (#72) 2026-04-08 14:09:01 -04:00
Ben Senescu
15cd01f89f fix: tweak email verification copy (#68)
* Improve verify email page copy and design

Remove redundant green success alert, update title to "Verify your email",
rename button to "Resend email", and change footer link to "Back to sign in".

* Fix verification emails not sending in production

Remove the backgroundTasks/waitUntil handler so better-auth awaits
email sending inline. Previously, errors thrown inside waitUntil
promises were silently dropped by Cloudflare Workers, preventing
emails from sending with no visible error logs.
2026-04-08 14:09:01 -04:00
Ben Senescu
b15b62cae6 Fix project-route auth gaps for domain and SERP lookups (#65) 2026-04-08 14:09:01 -04:00
Ben Senescu
5d093c1ebb feat: add sign out button to /subscribe 2026-04-08 14:09:01 -04:00
Ben Senescu
4b7b5b7f41 fix: go back to orignal wrangler.jsonc 2026-04-08 14:09:01 -04:00
Ben Senescu
a85f67c298 fix: remove better auth background tasks (#67)
* Improve verify email page copy and design

Remove redundant green success alert, update title to "Verify your email",
rename button to "Resend email", and change footer link to "Back to sign in".

* Fix verification emails not sending in production

Remove the backgroundTasks/waitUntil handler so better-auth awaits
email sending inline. Previously, errors thrown inside waitUntil
promises were silently dropped by Cloudflare Workers, preventing
emails from sending with no visible error logs.
2026-04-08 14:09:01 -04:00
Ben Senescu
a83940ddfc chore: add hosted Cloudflare deploy config (#66)
Keep app.openseo.so routes out of the default Wrangler config so the public Deploy to Cloudflare button continues to work.
2026-04-08 14:09:01 -04:00