Skip to content

pwv-vc/what-if

Repository files navigation

What If You Could

Communicate why your startup matters, not just how it works.

Live: https://what-if-you-could.pwv.ai
Source: https://www.val.town/x/pwv/what-if-you-could/


The Idea

The Problem

Too many pitches describe features, tech, or decks but never make clear what changes in the world if the product actually works.

In meetings at PWV, the same question kept coming up: "What if this works — why does it matter?"

The Pattern

Successful companies had obvious power from the "what if" alone:

  • Stripe: "What if you didn't need a business bank account to accept money online?"
  • Airbnb: "What if you could stay at anyone's place via Internet?"
  • Uber: "What if you could get a ride in SF via app instead of waiting 40 minutes?"

A strong "what if" does two things:

  1. Removes a painful limitation — bank accounts, hotels only, unreliable taxis — things people already hate.
  2. Is easy to reason about 5+ years out — if this were true at scale, the world looks different.

For early founders (especially weird/technical ones), this is the fastest way to decide: is there something potentially huge here, or is it just another feature?

The "What If" for This App

What if you could paste your startup pitch and instantly know whether you're building something that could change the world — or just another feature?

How It Works

flowchart LR
    A["Founder writes pitch"] --> B["AI reframes → 'What if you could…'"]
    B --> C["Published to browse page"]
    C --> D["Community upvotes, filters, discovers"]
    D --> E["Request intros · Apply to roles"]
    E --> F["PWV reviews & connects"]
Loading
  1. Pitch it. Describe what you're building and the problem it solves.
  2. It becomes a what if. Your pitch is distilled into a single "what if you could…" question — jargon-free, outcome-focused.
  3. Share it. Others upvote, request intros, and apply to roles. Claim your pitch to manage it from your dashboard.

Discover & Connect

Pitches aren't just reframed — they're classified. AI assigns topic categories (1–3 from 22 curated labels) and four structured facets (problem domain, target user, outcome type, disruption pattern) so every pitch is findable, comparable, and part of a larger picture.

  • Categories answer "what topic is this?" — broad, multi-select labels useful for browsing and RSS feeds.
  • Facets answer "what kind of problem, audience, outcome, and approach?" — single-select per dimension, powering similarity matching and trending.
  • Trending surfaces the most active facets from the last 7 days.
  • Similar Pitches shows up to 6 related pitches scored by shared facets on each pitch detail page.

Categories

Each pitch is assigned 1–3 topic categories by AI when it's created. Categories are broad labels that describe what a pitch is about (e.g. "AI & Machine Learning", "FinTech", "Developer Tools"). There are 22 categories organized into 7 groups:

Group Categories
AI & ML AI & Machine Learning, LLM & Agents, AI Infrastructure, Computer Vision
DevTools & DevOps Developer Tools, DevOps & CI/CD, Cloud Infrastructure, Observability
Data & Analytics Data & Analytics, Databases
Security Cybersecurity
Verticals FinTech, HealthTech, EdTech, Climate Tech
Platform API & Platform, Open Source, No-Code / Low-Code, SaaS
Other Marketplace, Productivity, Social & Community

Categories are defined in shared/categories.ts. Each has a slug, display label, group, icon, and color.

How categories are assigned

During pitch creation, a single OpenAI call reframes the pitch and assigns categories in one shot. A background cron job (jobs/categorize-pitches.cron.ts) backfills older pitches that predate the feature, processing up to 15 every 5 minutes.

Browsing by category

Click a category badge on any pitch card to filter the list. A grouped dropdown in the browse header lets you pick from all 22 categories. The URL updates to /?category=slug (e.g. /?category=ai-ml) and the active filter is shown as a dismissible pill.

Available category slugs

Group Slugs
AI & ML ai-ml, llm-agents, ai-infrastructure, computer-vision
DevTools & DevOps developer-tools, devops-cicd, cloud-infrastructure, observability
Data & Analytics data-analytics, databases
Security cybersecurity
Verticals fintech, healthtech, edtech, climate-tech
Platform api-platform, open-source, no-code, saas
Other marketplace, productivity, social-community

Facets

Every pitch is automatically classified along four dimensions using AI structured outputs:

Dimension Examples Count
Problem Domain data-management, customer-acquisition, security-compliance 15 slugs
Target User developers, founders, consumers, enterprise-it 10 slugs
Outcome Type speed, cost-reduction, access, quality 8 slugs
Disruption Pattern automation, marketplace, ai-augmentation, self-serve 8 slugs

Facets are assigned when a pitch is created. A background cron job (jobs/facetize-pitches.cron.ts) backfills any pitches that were created before facets existed, processing up to 15 pitches every 5 minutes.

Categories vs facets

Categories answer "what topic is this?" — they're broad, multi-select labels useful for browsing and RSS feeds. Facets answer "what kind of problem, audience, outcome, and approach?" — they're single-select per dimension and power similarity matching and trending. A pitch about an AI developer tool might have categories ai-ml + developer-tools and facets like problem_domain:developer-productivity, target_user:developers.

Browsing by facet

Click any facet badge on a pitch card to filter the list. The URL updates to /?facet=dimension:slug (e.g. /?facet=problem_domain:data-management), and the active filter is shown as a dismissible pill.

Similar pitches

Each pitch detail page shows up to 6 similar pitches scored by shared facets: a matching problem domain is worth 2 points, and each other matching dimension adds 1 point. Results are cached for 60 seconds.

Trending

The trending bar highlights the most popular facet values from the last 7 days. It appears above the pitch list and surfaces what the community is thinking about right now.

How it works

  1. The API aggregates counts across all four facet dimensions for pitches created in the last 7 days
  2. The top 5 facets (by count) are returned
  3. Results are cached for 5 minutes to keep things fast

Clicking a trending pill filters the pitch list by that facet, using the same /?facet=dimension:slug mechanism.

RSS Feeds

Subscribe to pitches via RSS 2.0 feeds in your favorite reader. All feeds include each pitch's "what if" statement, current rank, upvote count, and hiring status.

Feed URL Description
Main /feed.xml Top 50 ranked pitches
Recent /feed/recent.xml Pitches from the last 24 hours
Category /feed/category/{slug}.xml Top 50 pitches in a single category

Category feeds use the same slugs listed in the Categories section above. An invalid slug returns a 404.

Testing with curl

# Main feed (top 50 ranked pitches)
curl -s https://what-if-you-could.pwv.ai/feed.xml

# Recent pitches (last 24 hours)
curl -s https://what-if-you-could.pwv.ai/feed/recent.xml

# Category feed (e.g. AI & Machine Learning)
curl -s https://what-if-you-could.pwv.ai/feed/category/ai-ml.xml

# Category feed (e.g. FinTech)
curl -s https://what-if-you-could.pwv.ai/feed/category/fintech.xml

What You Can Do

Action Description
Create a pitch Describe your startup → get a "what if you could…"
Browse & filter By sort order, category, facet, hiring status, claimed
Upvote HackerNews-style ranking (no account needed)
Claim Verify ownership via magic link → unlock hiring & dashboard
Request an intro PWV-reviewed introductions (investor, partner, advisor…)
Apply to a role Two-step flow: verify email → complete application
Search Full-text search across all pitches
Subscribe via RSS Main feed, recent, or per-category feeds
Dashboard Manage owned pitches, track intros, view applications
Admin panel Domain-gated admin tools for PWV team

Built in Public

Everything is published — the agent prompts, the moderation rules, and the entire codebase. No black boxes.


Technology

Architecture Overview

The app is a single-page application (SPA) with a server-rendered HTML shell, backed by a RESTful JSON API. Everything runs on Val Town (Deno serverless).

graph TB
    subgraph "Frontend (Browser)"
        SPA["React 18 SPA<br/>wouter router · Tailwind v4"]
    end

    subgraph "Backend (Val Town / Deno)"
        HONO["Hono HTTP framework<br/>index.http.tsx"]
        ROUTES["API Routes<br/>pitches · claims · auth · search<br/>trending · stats · feed · og · admin"]
        AI["OpenAI Integration<br/>gpt-4o-mini structured outputs"]
        DB["SQLite (Turso/libSQL)<br/>val-scoped database"]
        EMAIL["Email (Val Town SDK)<br/>magic links · notifications"]
        CACHE["In-memory cache<br/>TTL-based"]
    end

    subgraph "Background Jobs"
        CRON1["categorize-pitches.cron.ts<br/>every 5 min"]
        CRON2["facetize-pitches.cron.ts<br/>every 5 min"]
        CRON3["cleanup-job.cron.ts<br/>expired codes & sessions"]
    end

    SPA <-->|"JSON API"| HONO
    HONO --> ROUTES
    ROUTES --> AI
    ROUTES --> DB
    ROUTES --> EMAIL
    ROUTES --> CACHE
    CRON1 --> AI
    CRON1 --> DB
    CRON2 --> AI
    CRON2 --> DB
    CRON3 --> DB
Loading

Tech Stack

Layer Technology
Runtime Deno (Val Town serverless)
HTTP framework Hono
Frontend React 18 (via esm.sh CDN)
Routing (client) wouter
Styling Tailwind CSS v4 (browser build) + custom PWV design tokens
Database SQLite via Turso/libSQL (std/sqlite@14-main)
Full-text search FTS5 virtual table (porter + unicode61 tokenizer)
AI OpenAI gpt-4o-mini with structured outputs (Zod schemas)
Email Val Town SDK (@valtown/sdk)
OG images Server-side SVG generation (/api/og/:id)
RSS RSS 2.0 with Atom self-link
Auth Passwordless magic links + session cookies
Rate limiting IP-based (SQLite-backed) + per-email cooldowns

Entity Relationship Diagram

erDiagram
    PITCHES {
        text id PK "8-char short ID"
        datetime created_at
        datetime updated_at
        text original "Raw pitch text"
        text what_if "AI-reframed statement"
        int upvotes
        text priority_claim_token UK
        datetime claim_window_expires
        bool claimed
        text hiring_status "not_hiring | hiring | hiring_paused"
        text hiring_roles "JSON array"
        text source "web | fund_application"
        text metadata "JSON blob"
        text categories "JSON array of slugs"
        text problem_domain "Facet slug"
        text target_user "Facet slug"
        text outcome_type "Facet slug"
        text disruption_pattern "Facet slug"
    }

    ACTORS {
        text id PK "UUID"
        datetime created_at
        datetime updated_at
        text email UK
        text person_name
        text person_role
        text organization_name
        text linkedin_url
        text website_url
    }

    INTERACTIONS {
        text id PK "UUID"
        datetime created_at
        datetime updated_at
        text pitch_id FK
        text actor_id FK
        text interaction_type "owner | intro_requested | job_applicant"
        text intro_reason "investor | partner | customer | advisor | …"
        text applied_role
        bool completed
        text notes
    }

    VERIFICATION_CODES {
        text id PK "UUID"
        text email
        text code UK
        text intent "claim_owner | request_intro | apply_job | dashboard_access"
        text pitch_id FK
        datetime expires_at
        bool verified
    }

    SESSIONS {
        text id PK "UUID"
        text actor_id FK
        text token UK
        datetime expires_at
    }

    IP_RATE_LIMITS {
        text id PK "UUID"
        text ip_address
        text action "pitch_submit | vote | email_request"
        int count
        datetime window_start
    }

    PITCHES_FTS {
        text pitch_id
        text what_if
        text original
    }

    PITCHES ||--o{ INTERACTIONS : "has"
    ACTORS ||--o{ INTERACTIONS : "participates"
    ACTORS ||--o{ SESSIONS : "has"
    PITCHES ||--o{ VERIFICATION_CODES : "references"
    PITCHES ||--|| PITCHES_FTS : "indexed in"
Loading

User Journeys

Submit & Claim a Pitch

sequenceDiagram
    actor Founder
    participant App
    participant AI as OpenAI
    participant DB as SQLite
    participant Email as Val Town Email

    Founder->>App: POST /api/pitches {pitch, email}
    App->>AI: Moderate + reframe + categorize + facetize
    AI-->>App: {what_if, categories, facets}
    App->>DB: INSERT pitch + actor + owner interaction
    App->>Email: Send claim link (magic link)
    App-->>Founder: {pitch_id, what_if, claim_url}

    Founder->>App: GET /validate?token=xxx
    App->>DB: Verify code + create session
    App-->>Founder: Set cookie → redirect to /pitch/:id
    Founder->>App: PATCH /api/pitches/:id/hiring
    App->>DB: Update hiring_status + roles
Loading

Request an Intro (Anonymous)

sequenceDiagram
    actor Visitor
    participant App
    participant DB as SQLite
    participant Email as Val Town Email

    Visitor->>App: POST /api/intros {pitch_id, email}
    App->>DB: Create actor + pending interaction
    App->>Email: Send verification magic link
    Email-->>Visitor: Click link

    Visitor->>App: GET /validate?token=xxx&intent=request_intro
    App->>DB: Verify code + create session
    App-->>Visitor: Redirect → /intros/:id/complete

    Visitor->>App: PATCH /api/intros/:id/complete {reason, notes}
    App->>DB: Update interaction (completed=true)
    App->>Email: Notify PWV team
Loading

Project Structure

├── index.http.tsx              # Hono app — HTML shell + all API routes
├── frontend/
│   ├── main.tsx                # SPA entry point (React + wouter router)
│   ├── components/
│   │   ├── App.tsx             # Homepage (trending bar + pitch list)
│   │   ├── Create.tsx          # Pitch submission form
│   │   ├── ViewPitch.tsx       # Single pitch view + actions
│   │   ├── SearchPage.tsx      # Full-text search
│   │   ├── Dashboard.tsx       # User dashboard (owned pitches, intros, apps)
│   │   ├── Profile.tsx         # User profile editor
│   │   ├── Why.tsx             # /why page — story + how it works
│   │   ├── FAQ.tsx             # /faq page — detailed Q&A
│   │   ├── Admin.tsx           # Domain-gated admin panel
│   │   ├── PitchList.tsx       # Paginated pitch list with filters
│   │   ├── PitchCard.tsx       # Single pitch card (browse view)
│   │   ├── TrendingBar.tsx     # Trending facets bar
│   │   ├── SimilarPitches.tsx  # Related pitches on detail page
│   │   └── ...                 # Layout, Header, Footer, forms, badges
│   ├── lib/                    # Client-side helpers
│   ├── style.css               # Custom CSS (PWV design system)
│   └── examples.ts             # Sample pitches for empty states
├── backend/
│   ├── routes/
│   │   ├── pitches.ts          # CRUD + vote + similar
│   │   ├── claims.ts           # Claim, intro, application flows
│   │   ├── auth.ts             # Login, logout, verify, session
│   │   ├── users.ts            # Profile + dashboard
│   │   ├── search.ts           # FTS5 full-text search
│   │   ├── trending.ts         # Trending facet aggregation
│   │   ├── stats.ts            # Platform statistics
│   │   ├── feed.ts             # RSS 2.0 feeds (main, recent, category)
│   │   ├── og.ts               # Dynamic OG image generation (SVG)
│   │   ├── admin.ts            # Admin routes (domain-gated)
│   │   └── webhooks/           # External webhook handlers
│   └── cache.ts                # In-memory TTL cache
├── lib/
│   ├── db-schema.ts            # Schema migrations (v1–v11)
│   ├── pitches/                # Pitch queries (list, get, create, search)
│   ├── ai.ts                   # OpenAI integration (reframe, moderate, classify)
│   ├── auth.ts                 # Session + verification code management
│   ├── rate-limit.ts           # IP rate limiting
│   ├── types.ts                # Core TypeScript interfaces
│   ├── utils.ts                # Shared utilities
│   ├── notifications.ts        # Email notification helpers
│   ├── emails/                 # React-email templates
│   └── admin-queries/          # Admin-specific database queries
├── shared/
│   ├── config.ts               # App URLs, branding, constants
│   ├── categories.ts           # 22 curated category definitions
│   ├── facets.ts               # 4-dimension facet taxonomy
│   ├── error-codes.ts          # Shared error code definitions
│   └── admin.ts                # Admin domain-gate config
├── jobs/
│   ├── categorize-pitches.cron.ts  # Backfill categories (every 5 min)
│   ├── facetize-pitches.cron.ts    # Backfill facets (every 5 min)
│   └── cleanup-job.cron.ts         # Expire old codes & sessions
├── prompts/                    # Published AI prompts (markdown)
├── types/                      # Additional type declarations
└── docs/                       # Design docs & ideas

AI Integration

All AI calls use OpenAI gpt-4o-mini with Zod-validated structured outputs:

Call When What it does
Moderation Pitch creation Checks content policy (prohibited topics, quality, on-topic)
Reframe + Categorize + Facetize Pitch creation Single call: generates "what if", assigns 1–3 categories, classifies 4 facets
Categorize backfill Cron (every 5 min) Backfills categories for older pitches (batch of 15)
Facetize backfill Cron (every 5 min) Backfills facets for older pitches (batch of 15)

Ranking Algorithm

Pitches are ranked using a HackerNews-style hot score:

score = (upvotes - 1) / (age_in_hours + 2) ^ 1.8

The gravity constant (1.8) ensures fresh pitches with a few upvotes rank above older pitches with many upvotes. Sort modes: 🔥 Hot (default), ⬆️ Top, ✨ New.

Authentication

Passwordless magic link flow:

  1. User enters email → verification code emitted → magic link sent
  2. Click link → code verified → session created (30-day cookie)
  3. Session cookie (HttpOnly, Secure) validates all authenticated requests
  4. Magic links expire after 15 minutes

Rate Limiting

Action Limit Window
Pitch submit 5 per hour per IP
Vote 20 per hour per IP
Email request 10 per hour per IP

API Endpoints

Full API reference (click to expand)

Pitches

  • POST /api/pitches — Create a pitch (AI reframe + classify)
  • GET /api/pitches — List pitches (paginated, filterable)
    • ?category=ai-ml — Filter by category slug
    • ?facet=problem_domain:data-management — Filter by facet (dimension:slug)
  • GET /api/pitches/:id — Get single pitch
  • POST /api/pitches/:id/vote — Upvote
  • PATCH /api/pitches/:id/hiring — Update hiring status/roles
  • GET /api/pitches/:id/similar — Similar pitches by shared facets

Search & Discovery

  • GET /api/search?q=... — Full-text search
  • GET /api/trending — Trending facets (7-day window). Returns:
    {
      "window": "7d",
      "facets": [
        { "dimension": "problem_domain", "slug": "data-management", "count": 12 },
        { "dimension": "target_user", "slug": "developers", "count": 9 }
      ]
    }
  • GET /api/stats — Platform statistics

Claims & Interactions

  • POST /api/claims — Request pitch ownership
  • POST /api/intros — Request intro to founder
  • GET /api/intros/:id — Get intro details
  • PATCH /api/intros/:id/complete — Complete intro request
  • POST /api/applications — Apply to a role
  • GET /api/applications/:id — Get application details
  • PATCH /api/applications/:id/complete — Complete application
  • POST /api/claim-link — Resend claim email

Auth

  • POST /api/auth/login — Request magic link
  • GET /api/auth/verify — Verify magic link token
  • GET /api/auth/session — Validate current session
  • POST /api/auth/logout — End session
  • POST /api/auth/resend — Resend verification email

Users

  • GET /api/users/me — Get current user profile
  • PATCH /api/users/me — Update profile
  • GET /api/users/me/dashboard — Dashboard data

Feeds

  • GET /feed.xml — Main RSS feed
  • GET /feed/recent.xml — Recent pitches feed
  • GET /feed/category/:slug — Category feed

Admin (domain-gated)

  • GET /api/admin/... — Admin query endpoints

Webhooks

  • POST /api/webhooks/fund-application — External fund application intake

Database

  • Engine: SQLite via Turso/libSQL (val-scoped)
  • Schema version: v11 (incremental, non-destructive migrations from v5+)
  • Tables: 6 core tables + 1 FTS5 virtual table
  • Search: FTS5 with porter stemming + unicode61 tokenizer
  • Indexes: Composite indexes for ranking, facet matching, rate limiting
  • Triggers: Auto-update updated_at on pitches, actors, interactions

Security

  • Parameterized SQL queries (no injection)
  • IP rate limiting (SQLite-backed)
  • AI content moderation (3-check pipeline)
  • Magic link expiration (15 minutes)
  • Session expiration (30 days)
  • HttpOnly secure cookies
  • Admin routes domain-gated by email

License

See LICENSE and NOTICE.

Built by PWV · Deployed on Val Town

About

PWV take on what ifs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors