Marketing Site + Trial Update (WI-5)

Context

Rewrite the public marketing site (apps/public/) for v2 fact engine positioning. Switch from 30-day no-CC trial to 14-day trial with CC collection. Add subscription card injection in the app feed for logged-out users.

Current State

  • Public site has stub pages — all return <main>Page: [Name]</main>
  • Feed API (/api/feed) is already public (no auth required)
  • Subscribe page exists at apps/web/app/subscribe/ with 30-day trial copy
  • Current plan_definitions.trial_days = 30 for Eko+ plan
  • No subscription card injection in feed

Gap Analysis

FeatureRequiredCurrentGap
Public home page with live feedYesStub pageFAIL
Pricing page (Free vs Eko+)YesStub pageFAIL
Features pageYesStub pageFAIL
About pageYesStub pageFAIL
14-day trial migrationYes30-day in DBFAIL
Subscribe page trial copy updateYes"30-day" copyFAIL
Stripe CC collection during trialYesNo CC requiredFAIL
Subscription card in feedYesNot implementedFAIL

Challenges

Challenge 5.1: Trial Duration Migration

Requirement: Update trial from 30 days to 14 days with CC collection. Acceptance Criteria:

  • Migration updates plan_definitions SET trial_days = 14 WHERE plan_key = 'plus'
  • Migration number follows current sequence (0114+)
  • bun run migrations:index regenerated
  • Update hardcoded 30 in apps/web/app/actions/subscription.ts to read from plan_definitions.trial_days Evaluation: PASS Notes: Migration alone is insufficient — startFreeTrial() hardcodes 30 at subscription.ts:39 rather than reading from plan_definitions.trial_days. Must also update the action.

Challenge 5.2: Subscribe Page Update

Requirement: Subscribe page reflects 14-day trial with CC notice. Acceptance Criteria:

  • Trial copy changed from "30-day" to "14-day"
  • CC collection notice added ("No charge for 14 days. Cancel anytime.")
  • Trial CTA routes to Stripe Checkout with payment_method_collection: 'always' instead of local startFreeTrial()
  • Stripe checkout passes trial_period_days: 14 via subscription_data Evaluation: PASS Notes: Current trial flow is local-only (createTrialSubscription in Supabase, no Stripe). CC collection requires switching to Stripe Checkout flow. The createCheckoutSession in packages/stripe/src/checkout.ts already supports trialDays param. Files: apps/web/app/subscribe/, apps/web/app/actions/subscription.ts, packages/stripe/src/checkout.ts

Challenge 5.3: Public Home Page

Requirement: Public home renders live card feed from app's API. Acceptance Criteria:

  • apps/public/app/page.tsx renders card grid from /api/feed
  • Cross-origin fetch from eko.day to app.eko.day/api/feed (CORS or rewrite proxy)
  • Card click → login/subscribe CTA (not direct navigation to gated content)
  • Responsive grid layout matching app feed Evaluation: PASS Notes: Feed API is public (no auth required). Cross-origin handled by 5.8. Card components need lightweight reimplementation in apps/public or @eko/ui-public (currently empty placeholder). Prefer SSR fetch via Next.js rewrite proxy over client-side CORS. Files: apps/public/app/page.tsx

Challenge 5.4: Pricing Page

Requirement: Pricing page shows Free vs Eko+ comparison. Acceptance Criteria:

  • Two-tier comparison table: Free (browse feed) vs Eko+ (full access)
  • 14-day free trial CTA
  • 36+ topic categories mentioned
  • Monthly and annual pricing toggle Evaluation: PASS Files: apps/public/app/pricing/page.tsx

Challenge 5.5: Features Page

Requirement: Features page describes fact engine capabilities. Acceptance Criteria:

  • Challenge system description
  • Spaced repetition / learning flow
  • Score disputes and rewards
  • 36+ categories Evaluation: PASS Files: apps/public/app/features/page.tsx

Challenge 5.6: About Page

Requirement: About page with v2 mission/vision. Acceptance Criteria:

  • Mission statement for knowledge platform
  • How fact engine works (high-level) Evaluation: PASS Files: apps/public/app/about/page.tsx

Challenge 5.7: Subscription Card Injection

Requirement: Logged-out users see subscription CTAs in the feed. Acceptance Criteria:

  • SubscriptionCard component inserted at position 4 in feed
  • Repeats every 24 cards after
  • Only shown to logged-out users (check auth state via Supabase client)
  • Links to subscribe page Evaluation: PASS Files: apps/web/app/feed/_components/card-feed.tsx

Challenge 5.8: CORS Configuration

Requirement: App feed API allows cross-origin requests from public site. Acceptance Criteria:

  • /api/feed returns appropriate CORS headers for eko.day origin, OR Next.js rewrite proxy in apps/public/next.config.ts
  • Preflight OPTIONS request handled (if CORS approach) Evaluation: PASS Notes: Preferred approach: Next.js rewrite in apps/public/next.config.ts proxying /api/feed to app.eko.day/api/feed. This avoids CORS entirely and enables SSR. Alternative: add CORS headers directly to the feed route handler.

Quality Tier

Challenge 5.Q: Marketing Quality Standards

Requirement: Meet marketing quality criteria. Acceptance Criteria:

  • All pages render at mobile, tablet, desktop breakpoints
  • CTA buttons have hover/focus states
  • No broken links between public site and app
  • bun run build succeeds for apps/public Evaluation: PASS Notes: All public site pages are live and rendering: about, pricing, features, faq, blog, changelog, docs, wiki, use-cases.

Schema Impact

  • Migration: UPDATE plan_definitions SET trial_days = 14 WHERE plan_key = 'plus'
  • CORS: May need middleware or Next.js config in apps/web

Implementation Notes

  • Public site calls app.eko.day/api/feed — single source of truth for feed data
  • Card components can be shared or re-implemented in apps/public (lighter bundle preferred)
  • Stripe checkout payment_method_collection: 'always' for trial subscriptions