# Paiduay — next sprint menu (2026-09-17)

*Decision-grade, for James next week. Sources: spec §9.58–§9.60, the sprint bridge, `PROD-PROOF-2026-09-17.md`, `QA-MVP.md`, `QA-v3-r2.md`, the plans here. Hours are the plans' own unless marked "est."; no other number is invented.*

## 1. Where we are

1. **A seller can do the whole thing alone on a phone:** tweet → `/c/new` → Clerk sign-up → handle → photo → line, price, LINE, verbs → optional reviewed English → publish → URL, image kit, tagged captions → edit, pause, resume from `/me`, where she sees her own opens and taps.
2. **Money exists but is switched off:** Pro is ฿499 once for 12 months (all kit templates + night versions), paid by PromptPay slip and approved by hand in `/admin/pro`. `PAIDUAY_PROMPTPAY_ID` is unset, so `/c/<handle>/pro` prints "ยังไม่เปิดรับชำระ" and nobody can pay. Revenue to date: ฿0.
3. **Proven on production** (`paiduay.6326638.xyz`, headless iPhone, Thai): 24/26 stages, both failures fixed the same night; `/c/new` → done in 51 s machine time, server waits 1.46 s and 0.83 s; report → hide → 404 → restore works; James's phone buzzed.
4. **Safety and law are honest:** O23 closed, footer line on every card, `/report` → `/admin/report`, safety battery 102/102 TH+EN, PDPA erasure (tombstone keeps the handle), 90-day photo retention, SEO minimum, AA contrast.
5. **The box is shared and public:** wildcard + apex point at the VPS; four demo apps sit beside Paiduay, Luna and TuaTon on one Caddy and one Postgres. Wave 4 lands tonight.

## 2. What blocks first revenue

Only what literally stands between a tweet and a paid Pro.

| # | Blocker | Who | Minutes | Where | Why it blocks |
|---|---|---|---|---|---|
| B1 | **A real domain** | James | 30 (buy + zone) + agent 2 h plumbing | `public-flip.md` §1–§2 steps 1–6 (`paiduay.app`; `.me` if premium); agent: public-flip WO4 (`deploy/Caddyfile` block, 308 from the number host, `NEXT_PUBLIC_SITE_URL`, `WIDGET_ALLOWED_ORIGINS`) | A number-domain "reads as a scam in a cold DM" (plans index); LINE and X print it under her photo; every bio link breaks the day we move; and Clerk production **binds to the domain** (CNAMEs), so B2 cannot go first. |
| B2 | **Clerk production instance** | James | ~30 | `apps/paiduay/docs/CLERK-SETUP.md` steps 1–9: production instance → DNS CNAMEs → `pk_live`/`sk_live` into `~/apps/ai-new-business/.env` **before** `deploy.sh` (publishable key inlined at build). And *User & Authentication → Email, phone, username*: **Username OFF**. | The wall prints "Secured by Clerk · **Development mode**" in orange at the moment she decides whether to trust us with her face, and asks for a **username** she must invent — it is not her handle (PROD-PROOF). Both are dashboard-only. Sellers who sign up on dev get a new `userId` on prod and must be re-bound by hand (`/admin/card`, 5 min each). Switch **before** the tweet. |
| B3 | **`PAIDUAY_PROMPTPAY_ID`** | James | 5 + agent 30 min | Server `.env` → `docker compose up -d paiduay`. Then one copy line under the QR: "ชื่อผู้รับที่แสดงในแอปธนาคาร: <legal name>" in `content/copy-pro.ts` (runbook §5 — a QR with no name behind it is a scam tell). | No id, no QR, no Pro. Her bank app shows *your* registered name, not "ไปด้วย" — say so first. |
| B4 | **Demo apps' prefilled password on the same public box** | James (decide) + agent 20 min | `apps/{demo,clinic-demo,barber-demo,cameo-demo}/app/login/page.tsx` prefill `NEXT_PUBLIC_DEMO_LOGIN_PASSWORD`; `demo.6326638.xyz/login` ships it in HTML. Fix: unset on the server + rebuild, or `docker compose stop demo clinic barber cameo` for launch week, or Caddy `basic_auth` as Luna has. | A seller or reporter who explores the domain finds four apps with an open operator login beside the one asking for her photo. Also the first CPU to shed if the tweet goes wide (runbook §7). |
| B5 | **The operator's daily loop being real** | James | 15/day | `launch-runbook.md` §4: `/admin/report` (hide-first on "not me"), `/admin/pro` (฿499.00, date after request, receiver = you; readable decline reasons), the n8n buzzes, `/admin/card` for rule-breakers. `PAIDUAY_SUPPORT_EMAIL` must be an inbox you open. | An unapproved slip is a refund request; an unread report breaks a printed 24-hour promise. Correction from the LINE plan: `pro.approved` does **not** buzz her — you type one LINE line by hand after approving; that is the first seller push, and better trust at the money moment than a bot. |

Order: B1 → B2 (same hour) → B3 → B4 → whole-flow test on your own phone on cellular (runbook §1 item 7) → tweet.

## 3. The next sprint menu

**In flight tonight (Wave 4, `apps/paiduay/docs/MVP-BRIEF-WAVE4.md`) — not candidates; check `git log` before ordering anything below:** A16 widget on v3 · A18 cards on `/browse`, Pro as a labelled paid placement ("featured" becomes true) · A20 DB pool audit · A21 video kit v1 (three silent clips, Pro-gated) · A22 slip pre-read (vision + EasySlip plug) · A23 `meetup.unanswered` +4 h nudge · A24 card plate `<picture>`/WebP · A28 Clerk `user.deleted` → forget · A30 nightly uploads backup. Status at commit time: §6.

Ranked by revenue leverage ÷ hours. Pick one or two.

| # | What | Why now | Hours | Plan | Decision it needs |
|---|---|---|---|---|---|
| 1 | **LINE Login via Clerk** (+ the LINE hint copy already shipped in `03773f1`) | Sign-up is the only step in tweet → publish → Pro that asks her for something she does not already have; email + password + code from a stranger's link is the biggest drop. LINE is already open. Dashboard only, gated on B1/B2. | James ~40 min; agent 0 (1 h if the LINE-only-account path needs a copy line) | `line-channel.md` §1.1, §2 | Provider name **ไปด้วย** (permanent, printed on the consent screen); accept email-less accounts (LINE is then the only way back in) or wait for LINE's email permission. |
| 2 | **Analytics N5 residual + N7 digest** — `publishedAt` stamped on first publish, `ps` cookie → `data.source` on `/c/new`, Monday 09:00 digest through the outbox to your phone | Without `publishedAt`/source, "which tweet worked" is a guess; the week-1 reading (§4) needs it. `/admin/numbers` and the `/go` hops exist (§9.60 d). | Opus 2–2.5 (plan: 1.5 + 1) | `analytics.md` §6 N5, N7 | None new — source codes `tt x th ig li fb qr direct` are the taught vocabulary; per-post tags only on your own posts. |
| 3 | **Hide → photos gone now** | PROD-PROOF found the photo URL still answers `200 immutable` after an operator hide; `/report` promises the page is hidden within 24 h, and the purge job waits 90 days (`lib/cards/purge.ts PURGE_STATUSES`). A "not me" claimant's face stays reachable for three months. | Opus 1–2 (est.) | `trust-safety.md` §3, `photo-pipeline.md` §3 | Operator hide purges immediately (default yes); owner pause does not (default — restore must be lossless). |
| 4 | **One-tap Pro approval** — EasySlip token wired to A22's `SlipVerifier`, duplicate-ref guard live, the receiver-name line (B3) | Turns the slip check into a glance; matters once approvals pass a handful. If A22 has not landed, this is A22. | James 15 min (token); Fable 1–2 (est.) | `payments.md` §3, §5 P2 | Buy an EasySlip plan or stay on vision-only pre-read until ~5 slips/week. |
| 5 | **Photo honesty v2** — one vision call per upload through the adapter: face box → focal point (`smartcrop-sharp` boost), "no face / several faces" messages, `photo.edits` line | Focal is centre-top today (no face detection); a badly framed plate is the first thing her followers see. Isolated, no DDL. | Fable 3–4 (plan stage 5 + §4) | `photo-pipeline.md` §1 stage 5, §4 | The photo may leave the box for one transient vision call (README default 6: yes); cut-out stays off. |
| 6 | **Shared-box hardening** — stop/gate the demos (B4), Cloudflare orange with `trusted_proxies cloudflare` + `CF-Connecting-IP`, OG/kit cache rule, per-IP limiters proven to still see real addresses | One Caddy and one Postgres serve Paiduay, Luna and TuaTon; a wide tweet hits them too. Orange **without** `trusted_proxies` folds every visitor into a few IPs and the 30/min limiter throttles everyone. A20 covers Postgres tonight. | Opus 3 (plan) + James 10 min | `public-flip.md` §2 "Cloudflare mode", §6 WO6; `launch-runbook.md` §7 | Grey or orange on day one (default grey until WO6 ships); demos: stop, password, or leave. |
| 7 | **`ModuleDef.schedules` seam** — a declarative boot-time schedule on the module so `ensureCardsPurgeSchedule` stops being called from `lib/cards/purge-job.ts`, `purge.ts` and `app/admin/card/page.tsx` | Three call sites and a TDZ-cycle workaround (§9.60 c) is the shape of the next bug; every future job (digest, video cache sweep) needs the seam. Zero revenue, one evening. | Opus 2 (est.) | spec §9.60 open list; `packages/core/src/sdk/module.ts` (`jobs?: JobDef[]`) | None — a §9 entry; kernel stays vertical-blind. |
| 8 | **Critic round 3 — real sellers** — a read-only critic on the first five real cards (not ours), 390 TH, then a file-owned fixer | Rounds 1–2 scored our own cards (79, 88, both fixed). The gaps real sellers hit — their photos, prices, LINE forms — are the only ones left that matter, and unknowable before five exist. | Fable 4–6 (est.) | `QA-MVP.md` method | Trigger: ≥ 5 published-with-photo cards from strangers. Not before. |

**Deferred on purpose** (each has a plan): video kit v2 (captions, 4:5; after A21 and a first Pro sale — `video-kit.md` V5), **matchmaking** (34 h, no revenue until booking fee (c)), **LINE OA push** (7 h + ฿888 badge; third by `line-channel.md` §2, only above ~5 approvals/week — today there is almost nothing to push to a card seller), **card-as-module** (12–21 h; when a second person-led vertical has a named prospect), trust-safety WO-4/5/6 (serve the `/p` pages; binding waits for the first "not me"), claim flow (needs OA + domain).

## 4. What to measure in week 1

The SQL and the reading are in `launch-runbook.md` §6 — run it, do not rewrite it: cards created, published, published-with-photo, Pro requests, Pro approved, reports, reports open, plus the per-day curve. Taps by kind and source: `/me` and `/admin/numbers` (§9.60 d).

**The two numbers that decide the sprint after** (runbook §6, James confirms): `published_with_photo` ≥ 10 in seven days means the tweet and the editor work; < 3 means the door is wrong (copy, sign-up friction, the host) — fix the door before tweeting again. `pro_approved` ≥ 1 is first revenue; 0 with ≥ 10 cards is a price-or-what-Pro-buys question, answered by asking three of those sellers on LINE, not by building. Longer horizon from `analytics.md` §4: alive cards ≥ 10 at day 30, Pro ≥ 3 by day 45.

## 5. What only James decides (defaults if you say "you choose")

1. **Domain:** `paiduay.app` now, before Clerk production (default). Or tweet on the number host, knowing the cost in B1.
2. **Clerk production before the tweet** (default yes) — or tweet on dev and re-bind sellers by hand.
3. **PromptPay id, and your legal name printed under the QR** (default: yes, printed).
4. **Demos during launch week:** stop the four containers (default), `basic_auth`, or leave.
5. **The sprint pick:** default = #1 (your 40 minutes) + #2 and #3 (one Opus evening), then wait for the two numbers.
6. **LINE provider name ไปด้วย** and email-less accounts (default: accept them).
7. **The cast:** Martin, Troy, Todd, Skinner, Krabappel, Itchy, Kirk, Wiggum, Lou, Snake — yes or rename.
8. **Support inbox and SLA as printed** (Mon–Fri 09–18; "not me" hidden within 24 h) — or change `content/copy-safety.ts`.
9. **Named PDPA controller** on `/policy` until a company exists (default: your own name, same as the PromptPay receiver).
10. **Thresholds** 10 cards / 1 Pro in week 1 — or your own.

## 6. Wave 4 status at commit time

`git log --oneline -20` at commit: no Wave 4 commit has landed; A16 (widget) and A20 (db pool) sit restored in the working tree, uncommitted. The orchestrator's §9.61 entry is the authority once written.
