chore: sync in-progress work across marketing, admin, API and tests

Snapshot of uncommitted work that had accumulated in the tree alongside
the Turnstile changes:

- marketing pages, SEO helpers (lib/seo.ts, lib/marketing/) and
  structured data
- admin billing actions and a per-user portfolio view, plus an admin
  error boundary
- rate limiting (lib/rate-limit.ts) applied across the /api/v1 surface
- CSP and proxy adjustments, accounting/webhook lib updates
- Playwright config and an e2e/unit test suite
- next bumped to ^16.3.4 with the lockfile regenerated
- generated AGENTS.md / CLAUDE.md

Authored by other sessions working in this tree; committed here so the
Turnstile work could be pushed without leaving the tree dirty.
Typecheck passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-09-05 16:27:16 -04:00
co-authored by Claude Opus 5
parent 8f90347659
commit 1d02598786
71 changed files with 3641 additions and 819 deletions
+17 -4
View File
@@ -35,6 +35,19 @@ export async function GET() {
return NextResponse.json(data)
}
// Shape of one item in the model's JSON response. Every field is optional
// because the model is not a trusted schema — the insert below supplies a
// fallback for each, so a missing key degrades instead of throwing.
type AiPrediction = {
type?: string
title?: string
prediction?: string
confidence?: string
timeframe?: string
risk_level?: string
data?: Record<string, unknown> | null
}
export async function POST() {
const user = await getSessionUser()
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
@@ -184,7 +197,7 @@ Only return valid JSON, no other text.`
json: true,
})
let predictions: any[] = []
let predictions: AiPrediction[] = []
try {
const parsed = JSON.parse(content || "{}")
predictions = Array.isArray(parsed) ? parsed : (parsed.predictions ?? [])
@@ -195,11 +208,11 @@ Only return valid JSON, no other text.`
// Replace old predictions
await db.delete(ai_predictions).where(eq(ai_predictions.user_id, ownerId))
const toInsert = predictions.map((p: any) => ({
const toInsert = predictions.map((p: AiPrediction) => ({
user_id: ownerId,
type: p.type ?? "growth_opportunity",
title: p.title,
prediction: p.prediction,
title: p.title ?? "Untitled prediction",
prediction: p.prediction ?? "",
confidence: p.confidence ?? "medium",
timeframe: p.timeframe ?? "Next 30 days",
risk_level: p.risk_level ?? "low",