* 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
* refactor: simplify keyword source selection flow
* fix: improve stacked keyword page layout
* fix: keep SERP tied to searched keyword
Stop silently falling back SERP lookups to related keywords and show a keyword-specific empty message so missing coverage is explicit to users.
* refactor: extract keyword controller and switch SERP to live
Move keyword research orchestration out of the route, call DataForSEO organic live SERP, and harden response normalization with runtime validation for safer typed handling.
* refactor: split keyword research page state into focused hooks
* fix: aggregate auto keyword fallback and reuse cached results
* scope keyword metrics to projectId
* refactor: modularize SEO routes and harden parsing for CI checks
* refactor: adopt zod json codecs at parse boundaries
* refactor: adopt papaparse, tldts, and zod error enums
* refactor: reorganize client and server feature layout
Group keyword UI/controller files and server repositories/services by feature while keeping serverFunctions as the stable API surface. This improves navigation and maintainability without changing runtime behavior.
* refactor: move PSI business logic into service layer
Keep serverFunctions/psi as thin transport handlers by delegating orchestration, export shaping, and source resolution to PsiAuditService. This aligns PSI with the existing service-first backend pattern without changing endpoint behavior.
* refactor: enforce 350/120 size limits across modules
* move self host
* fix: show dynamic range for search trends
Replace the static 'Past 12 months' subtitle with a computed range from the actual plotted trend points so the UI reflects data lag correctly.
* fix: bind audit workflow writes to workflow instance
* chore: make ci checks pass