diff --git a/docs/superpowers/specs/2026-06-12-pricing-waitlist-design.md b/docs/superpowers/specs/2026-06-12-pricing-waitlist-design.md
new file mode 100644
index 0000000..ea90a2d
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-12-pricing-waitlist-design.md
@@ -0,0 +1,197 @@
+# Pricing-page waitlist — design
+
+**Date:** 2026-06-12
+**Status:** Approved, pending implementation plan
+
+## Problem
+
+The `/pricing` page shows disabled **"Coming soon"** buttons on the Daily
+(`basic`) and Smart (`plus`) cards, because their alerting features aren't
+shipped yet (`COMING_SOON = ['basic', 'plus']` in
+`resources/js/components/PricingGrid.vue`). We want to capture interest instead
+of showing a dead button: let a visitor leave their **name + email** to be
+notified when alerts launch.
+
+This list is **separate from registered users** — it's a marketing signup log,
+not an account.
+
+## Scope
+
+**In scope (now):**
+
+- Collect name + email from the pricing page.
+- Store in a dedicated table.
+- View + CSV export in the Filament admin panel (the "announce later" hook).
+
+**Out of scope (deliberately deferred to a later task):**
+
+- The announcement email itself — Mailable, queued bulk send, unsubscribe
+ route/link. We collect now and build the send mechanism when we're actually
+ ready to email the list.
+- Tier segmentation — we do **not** record which tier (Daily vs Smart) the
+ visitor was interested in. One flat list.
+- IP / source / user-agent capture. Can be added later if abuse becomes a
+ problem; not needed for v1.
+
+## Why not the `offload-project/laravel-waitlist` package
+
+That package is built around referrals, queue positions, and invite flows —
+none of which this needs. It would add a dependency and migrations we'd work
+against. A single table + endpoint is less code and less maintenance for
+"collect name + email, announce later."
+
+## Architecture
+
+Follows the existing shape: **Vue SPA → public REST API → thin controller →
+fat service → model** (`.claude/rules/architecture.md`,
+`.claude/rules/frontend.md`).
+
+```
+WaitlistForm.vue (below the pricing grid)
+ └── useWaitlist.js (composable, uses configured `api` axios instance)
+ └── POST /api/waitlist (public, throttle:10,1)
+ └── Api\WaitlistController@store
+ └── StoreWaitlistRequest (validation)
+ └── WaitlistService::subscribe()
+ └── WaitlistSubscriber (model)
+```
+
+## Backend
+
+### Migration — `create_waitlist_subscribers_table`
+
+| Column | Type | Notes |
+|--------------|-----------------------|-------------------------------|
+| `id` | bigIncrements | |
+| `name` | string | |
+| `email` | string, **unique** | stored lowercased + trimmed |
+| `created_at` / `updated_at` | timestamps | |
+
+No other columns, per the minimal-schema decision.
+
+### Model — `WaitlistSubscriber`
+
+- `$fillable = ['name', 'email']`.
+- A factory (`WaitlistSubscriberFactory`) for tests.
+
+### Service — `WaitlistService`
+
+```php
+public function subscribe(string $name, string $email): WaitlistSubscriber
+```
+
+- Normalises email: `trim` + `strtolower`.
+- `firstOrCreate(['email' => $email], ['name' => $name])` — **idempotent**.
+ Re-submitting an existing email is a no-op success, never an error and never
+ a duplicate row. Does not reveal whether the email was already present.
+- `final` class, constructor injection only (per `code-style.md`).
+- Returns the `WaitlistSubscriber` model (typed return, per `architecture.md`).
+
+### Form Request — `Api/StoreWaitlistRequest`
+
+```php
+'name' => ['required', 'string', 'max:255'],
+'email' => ['required', 'email', 'max:255'],
+```
+
+`authorize()` returns `true` (public endpoint).
+
+### Controller — `Api\WaitlistController@store`
+
+- Thin: validates via `StoreWaitlistRequest`, calls `WaitlistService::subscribe()`.
+- Returns `201`:
+ ```json
+ { "message": "You're on the list — we'll email you when alerts go live." }
+ ```
+- Does **not** echo the model back (nothing sensitive to return; no API
+ Resource needed).
+
+### Route — `routes/api.php`
+
+Added to the **public** block (alongside `/auth/register`):
+
+```php
+Route::post('/waitlist', [WaitlistController::class, 'store'])
+ ->middleware('throttle:10,1');
+```
+
+Public (no API key, no Sanctum auth), but throttled to 10/min per IP. Uses the
+same Sanctum-stateful XSRF path as `/auth/register`, so the SPA posts to it with
+its existing axios credentials/XSRF config.
+
+## Frontend
+
+### Composable — `resources/js/composables/useWaitlist.js`
+
+Exposes:
+
+- `submit(name, email)` — `POST /waitlist` via the configured `api` instance
+ (`resources/js/axios.js`); never a bare axios/fetch call (per `frontend.md`).
+- `loading` (ref bool)
+- `error` (ref string|null) — surfaces validation/throttle errors
+- `success` (ref bool)
+
+### Component — `resources/js/components/WaitlistForm.vue`
+
+- A single shared "band" rendered **once below the pricing grid**.
+- Heading (e.g. "Want in when alerts launch?") + `name` input + `email` input +
+ "Notify me" button, laid out inline on the band.
+- States: idle → loading (button disabled/spinner) → success (inline
+ "You're on the list ✓", form hidden/replaced) or error (message shown,
+ inputs retained).
+- Tailwind styling consistent with the pricing cards
+ (`.claude/rules/code-style.md`, Tailwind v4).
+
+### `resources/js/components/PricingGrid.vue`
+
+- **No change** to the `COMING_SOON` logic — the Daily/Smart buttons stay as
+ disabled "Coming soon".
+- Render `` once, below the `.grid` (still inside the section).
+
+## Admin — Filament `WaitlistSubscriberResource`
+
+In `app/Filament/Resources/` (matching siblings like `UserResource`).
+
+- **Table:** `name`, `email`, joined date (`created_at`). Searchable on name +
+ email; default sort newest first.
+- **CSV export:** Filament table export action — this is the mechanism for
+ pulling the list to announce later.
+- **Read-only:** no create / edit / delete actions. It's a signup log; rows
+ arrive only via the public endpoint.
+
+## Tests (Pest — `.claude/rules/testing.md`)
+
+**Feature** (`tests/Feature/`, `RefreshDatabase`, factory-first):
+
+- `POST /api/waitlist` with valid name + email → `201`, row exists.
+- Duplicate email → `201`, still exactly one row (idempotent, no error).
+- Missing name → `422`.
+- Invalid email → `422`.
+- (Throttle middleware present on the route.)
+
+**Unit** (`tests/Unit/Services/WaitlistServiceTest.php`):
+
+- `subscribe()` lowercases + trims the email before storing.
+- `subscribe()` called twice with the same email creates one row and returns
+ the existing subscriber.
+
+## Files touched
+
+**New:**
+
+- `database/migrations/xxxx_create_waitlist_subscribers_table.php`
+- `app/Models/WaitlistSubscriber.php`
+- `database/factories/WaitlistSubscriberFactory.php`
+- `app/Services/WaitlistService.php`
+- `app/Http/Requests/Api/StoreWaitlistRequest.php`
+- `app/Http/Controllers/Api/WaitlistController.php`
+- `app/Filament/Resources/WaitlistSubscriberResource/...` (resource + list page)
+- `resources/js/composables/useWaitlist.js`
+- `resources/js/components/WaitlistForm.vue`
+- `tests/Feature/WaitlistTest.php`
+- `tests/Unit/Services/WaitlistServiceTest.php`
+
+**Modified:**
+
+- `routes/api.php` (add public throttled route)
+- `resources/js/components/PricingGrid.vue` (render `` below grid)