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/
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?"
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:
- Removes a painful limitation — bank accounts, hotels only, unreliable taxis — things people already hate.
- 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?
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?
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"]
- Pitch it. Describe what you're building and the problem it solves.
- It becomes a what if. Your pitch is distilled into a single "what if you could…" question — jargon-free, outcome-focused.
- Share it. Others upvote, request intros, and apply to roles. Claim your pitch to manage it from your dashboard.
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.
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.
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.
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.
| 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 |
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 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.
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.
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.
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.
- The API aggregates counts across all four facet dimensions for pitches created in the last 7 days
- The top 5 facets (by count) are returned
- 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.
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.
# 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| 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 |
Everything is published — the agent prompts, the moderation rules, and the entire codebase. No black boxes.
- Reframing prompt — how the pitch analyst agent generates "what if" statements
- Moderation prompt — content policy and error codes
- Categorization prompt — how categories are assigned
- Facet prompts — how structured dimensions are classified
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
| 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) |
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 |
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"
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
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
├── 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
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) |
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.
Passwordless magic link flow:
- User enters email → verification code emitted → magic link sent
- Click link → code verified → session created (30-day cookie)
- Session cookie (
HttpOnly,Secure) validates all authenticated requests - Magic links expire after 15 minutes
| Action | Limit | Window |
|---|---|---|
| Pitch submit | 5 | per hour per IP |
| Vote | 20 | per hour per IP |
| Email request | 10 | per hour per IP |
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 pitchPOST /api/pitches/:id/vote— UpvotePATCH /api/pitches/:id/hiring— Update hiring status/rolesGET /api/pitches/:id/similar— Similar pitches by shared facets
Search & Discovery
GET /api/search?q=...— Full-text searchGET /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 ownershipPOST /api/intros— Request intro to founderGET /api/intros/:id— Get intro detailsPATCH /api/intros/:id/complete— Complete intro requestPOST /api/applications— Apply to a roleGET /api/applications/:id— Get application detailsPATCH /api/applications/:id/complete— Complete applicationPOST /api/claim-link— Resend claim email
Auth
POST /api/auth/login— Request magic linkGET /api/auth/verify— Verify magic link tokenGET /api/auth/session— Validate current sessionPOST /api/auth/logout— End sessionPOST /api/auth/resend— Resend verification email
Users
GET /api/users/me— Get current user profilePATCH /api/users/me— Update profileGET /api/users/me/dashboard— Dashboard data
Feeds
GET /feed.xml— Main RSS feedGET /feed/recent.xml— Recent pitches feedGET /feed/category/:slug— Category feed
Admin (domain-gated)
GET /api/admin/...— Admin query endpoints
Webhooks
POST /api/webhooks/fund-application— External fund application intake
- 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_aton pitches, actors, interactions
- 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