Critique marketplace — roadmap
Three-tier plan for letting EmberKiln creators get feedback on their audiobook chapters. Tier 0 is shipping now; T1 + T2 are the planned next steps documented here so they don't get forgotten.
The whole thing rides on the existing polymorphic story_critiques table — same table that powers AI persona critiques on write.cafe submissions + short stories today.
T0 — AI single-shot critique (shipping)
A creator viewing their season on /seasons/[id] (owner or super-admin) sees a ChapterCritiquesPanel below the chapter list with a "✨ Get AI critique" button per chapter. Clicking it routes through the existing AI persona engine — a fresh persona reads the chapter prose and replies in their own voice (200–400 words, in character).
Pieces wired:
- Migration 337 —
story_critiques.target_kindCHECK extended to include'story_episode'. - Backend
story-critiques.ts—loadStoryTexthandles the new kind by readingstory_episodes.chapter_prose. - New routes on
/api/story-seasons/:id/episodes/:n—POST /critiquerequests a fresh critique (optionalpersona_id).GET /critiqueslists critiques on that chapter.GET /api/story-seasons/:id/critiqueslists across every chapter for a one-pane inbox view.
- Frontend
ChapterCritiquesPanel.tsx— mounted on the season detail page for novel-mode shows when the viewer is the owner or admin.
Pricing (T0): every show gets 1 free critique total. Past that the backend returns 402 with code: 'critique_quota_exceeded', and the panel swaps to a "Graphene+ unlocks unlimited" upgrade chip. Per-show (not per-chapter) so the upgrade pressure lands where the value actually scales — a 6-chapter show with one critic across all six chapters isn't the experience we're trying to sell.
Out of scope for T0:
- Persona browse / specialty picker — T1.
- Human editor marketplace — T2.
/studio/inboxaggregator across all the creator's shows — defer until T1 ships and the volume justifies a dedicated page.- Email digest of new critiques — defer until we have webhook/email infra wired.
T1 — Persona suite + bundles
Promote the critique experience from "a random persona will read your chapter" to "here's a curated suite of critics, each with a stated specialty — pick the lens you want." Same backend engine, much richer chooser.
T1.1 — Persona browse modal (shipping)
The first half of T1 — the picker UI + specialty taxonomy. Bundle pricing + credit packs are still TODO (see T1.2 below).
What landed:
- Migration 339 —
ai_personas.critique_specialtycolumn added (CHECK constrained to the 7 specialty values:pacing | voice | dialogue | structure | hook | specificity | general, plusNULLfor unstamped). Partial index on(critique_specialty, display_name) WHERE is_active = truefor the picker hot path. Backfill is intentionally NOT in the migration — super-admin stamps personas via the existing/dashboard/admin/ai-personasconsole. services/story-critiques.ts— newCritiqueSpecialtytype union +isValidCritiqueSpecialtytype guard +listCritiquePersonas({ specialty? })function. Returns{ id, display_name, mbti, critique_specialty, teaser }whereteaseris derived frombackstory.voice || backstory.core_tension(140-char slice).GET /api/story-seasons/critique-personas?specialty=X— mounts on the story-seasons router. Auth: any signed-in user (persona names aren't sensitive; the chapter critique POST that ultimately consumes the pickedpersona_idstill does the show-owner /is_creatorgate).components/seasons/PersonaPickerModal.tsx— modal opens from the "✨ Get AI critique" button. Filter pills at top (All / Pacing / Voice / Dialogue / Structure / Hook / Specificity / General), each persona card shows display name + MBTI + specialty badge + teaser + "Pick →" button. Includes a prominent "🎲 Surprise me with a fresh critic" fallback that pipes through to the T0 random-fresh logic so the picker never dead-ends a curious creator.ChapterCritiquesPanel— clicking "Get AI critique" now opens the picker (was: fired the critique directly).requestForChapter(episodeNumber, personaId)accepts an optionalpersona_id;null= the T0 random-fresh fallback.
What's intentionally simple:
- No auto-suggestion. The roadmap mentioned scoring personas by chapter weak-signal hints (e.g. "this chapter was flagged for pacing → show pacing critics first"). Skipping that — the manual filter pills are enough for the picker UX. We can add a "Recommended for this chapter" header later when we wire signal sources (
ImproveAllBehindBannerflags,analyzeChapterTitlesflags) into the personas endpoint. - No specialty seeding. Personas start with
critique_specialty=NULLand show up under "Other critics" in the grouped view. Super-admin stamps them via the existing persona console. - Same free-quota gate as T0 — 1 critique per show, then Graphene+ unlocks unlimited. Bundle pricing is T1.2.
T1.2 — Critique Pass: 4 specialists in one click (shipping)
Subscription-only v0 of bundles. Subscribers get unlimited Critique Passes; non-subscribers see the button styled as an upgrade prompt that routes to /studio/billing. No new SKU, no credit-balance UI — the marginal LLM cost (~$0.003 / pass) is too small to justify a parallel pricing surface, and the Graphene+ paywall is already the right gate.
What landed:
POST /api/story-seasons/:id/episodes/:n/critique-pass— fires the existingrequestCritiquePanel({ count: 4 })service against the chapter. Auth: show-owner +is_creator OR super_admin. Non-subscribers get 402 withcode='subscribe_for_bundle'so the panel can route to billing.- Critique Pass button on
ChapterCritiquesPanel— sits next to "Get AI critique." For subscribers: gradient purple→fuchsia button reading "🎁 Critique Pass · 4 critics" → click fires the API → spinner "… Rolling 4 critics" → 4 critiques append in one batch. For non-subscribers: same shape but with a 🔒 icon + "Critique Pass (Graphene+)" label → click routes to/studio/billing?next=…instead of the API. - Partial-success handling —
requestCritiquePanelreturns{ critiques, errors }. If 4 fire but only 3 land (one persona's LLM call errored), the panel renders the 3 + a "1 of 4 critics couldn't be reached" notice. Beats failing the whole batch.
What's intentionally simple:
- No credit packs. Marginal LLM cost is ~$0.003/pass. Charging a one-off SKU adds Stripe + balance UI complexity for a tiny revenue line. If demand surfaces from non-subscribers who want occasional passes without committing to $5/mo, we can layer credit packs on the existing
studio_credit_packsinfrastructure as a fast-follow. For now: subscribe to unlock, or stay on the 1-free single-critique tier. - No specialty rotation in the pass. The existing
requestCritiquePanelshuffles personas randomly. Doesn't pick "one from each specialty" — we'd need to extend the panel service to do that. Skipping for v0; the random shuffle already produces voice variety. - No bundle history view. The 4 critiques just append to the existing chapter critique list. No "this was part of a pass" badge. The critiques themselves carry their value standalone.
T1.3 — Specialty rotation in the pass (shipping)
The Critique Pass now pulls one persona from each of four distinct editorial angles instead of four random personas. The four takes feel deliberately varied — pacing, voice, dialogue, structure — rather than luck-of-the-shuffle.
What landed:
requestCritiquePanelgainsspecialtyDistribution?: string[]. When provided, the picker walks the list and pulls one fresh persona stamped with each specialty (in order). Per-specialty buckets are shuffled internally so repeated passes within the same specialty rotate fresh voices across runs.POST /:id/episodes/:n/critique-passpasses['pacing', 'voice', 'dialogue', 'structure']— the four most generally useful angles for a chapter. Hook + specificity are sharper for opening / line-level critique; we route those through the single-critique picker when a creator wants them specifically.- Graceful fallback. Specialties without an available persona silently fall through to a fresh random pick from the broader pool, so a corpus with no
pacing-stamped personas still returns 4 takes. Same Fisher-Yates shuffle as before, just for the leftover slots. - No frontend changes. The button still says "🎁 Critique Pass · 4 critics" — the specialty rotation is purely backend. Each delivered critique still carries persona name + body, and downstream the persona's specialty is visible via
ai_personas.critique_specialtyif a future UI wants to badge "pacing critic" / "voice critic" on each take.
Design notes:
- Why 4 specialties not 6: Hook + specificity are line-level / opening-specific. A 4-card pass that doesn't reach those angles still gives the writer a complete cross-section of chapter-level critique. We can extend to a "Deep Pass: 6 specialists" later if Graphene+ subscribers ask.
- Why not weight by chapter signal: The roadmap mentioned scoring personas by chapter weak-signal hints (e.g. ImproveAllBehindBanner pacing flag → pacing persona first). T1.3 v0 ships the canonical 4 regardless — the signal-driven prioritization belongs with a "Recommended for this chapter" header that doesn't exist yet. Layering it onto the pass would couple two design decisions before we know which one creators value.
- Backward compatibility: Existing callers of
requestCritiquePanel(write.cafe submissions, short stories) don't passspecialtyDistributionand still get the original random shuffle. No behavior change there.
T0.5 — Manual human queue (shipping)
In between T0 (AI single-shot) and T2 (paid editor marketplace) sits a much simpler shape: a free, manual queue of human critique requests. No payment, no editor onboarding, no marketplace surface — just a fair-game first-come, first-claim list at /write-cafe/critique-queue.
Use this to prove demand on both sides before building T2's marketplace weight.
Flow
- Creator clicks "🧑 Request human critique (free)" on a chapter row in
ChapterCritiquesPanel→ row inserted intocritique_requestswithstatus='pending'. - Any signed-in user visiting
/write-cafe/critique-queuesees pending requests with show + chapter context + a prose snippet teaser. - Editor clicks "Claim + write critique" → atomic claim → opens the chapter prose + a textarea.
- Editor delivers → backend inserts a row into
story_critiques(target_kind='story_episode',persona_id=NULL,persona_display_name=editor's display name) and stampsdelivered_critique_idback on the request. The critique then renders on the creator'sChapterCritiquesPanelside-by-side with AI critiques.
Schema
critique_requeststable (migration 338) — request id, polymorphic target, requester, optional note, status enum (pending|claimed|delivered|cancelled), claimer + claim time + delivery time + linkage back tostory_critiques.id.- No payment columns yet. T2 layers
price_cents,stripe_payment_intent_id,escrow_release_at,platform_fee_centson top of this table without a rewrite.
Backend
services/critique-requests.ts—createCritiqueRequest,listPendingRequests,listMyRequests,listMyClaimedRequests,claimCritiqueRequest(atomic),deliverCritiqueRequest(inserts intostory_critiques),cancelCritiqueRequest,getQueueHealth(used by the future Bluesky cron).routes/critique-requests.ts— mounted at/api/critique-requests. Endpoints:POST /(creator creates)GET /queue(decorated pending list for the queue page)GET /:id(single request + full chapter prose for the deliver view)POST /:id/claim,POST /:id/deliver,POST /:id/cancelGET /mine/created,GET /mine/claimed(per-user pivots)GET /health(for the auto-solicit cron)
Frontend
ChapterCritiquesPanelgains a second button per chapter row: 🧑 Request human critique (free). When a request is in flight on that chapter, the button is replaced by a status chip (🧑 On write.cafe queue/🧑 Claimed by an editor) so the creator doesn't double-submit.- New page
/write-cafe/critique-queue:- Pending list (newest first, max 50) with show + chapter context, prose snippet, creator note, "Claim + write critique" CTA.
- "Claimed by you" pile above pending (so an editor who navigated away mid-write can resume).
- Claim → opens the deliver view: chapter prose in a scrollable card + a textarea with 50–8000 char guidance.
- Deliver → inserts into
story_critiques+ flips the request todelivered.
Bluesky auto-solicit (planned, not built yet)
When the queue starts backing up, post on Bluesky to solicit fresh editors. Mechanism:
- New cron (15-min cadence) on the backend hitting
getQueueHealth. - Trigger condition:
pending_count >= 5ANDoldest_pending_seconds >= 60*60*24(24h). - Post template: "📚 The write.cafe critique queue has {N} chapters waiting for human eyes. Free for editors right now — sign in at writecafe.fm/critique-queue and pick one. Each is a single audiobook chapter, 600–4000 words."
- Use the existing
bluesky-autopostservice (already wired for the "📣 Announce" cafe panel). Add a new autopost target kind'critique_queue_solicit'so the post lands in the same audit trail as the other Bluesky posts. - Throttle: at most 1 solicit post per 24h regardless of queue depth. Don't become spam.
Open product questions for T0.5
- Anyone-can-claim vs. trust-floor: any signed-in user is permitted today. Should we eventually require something — minimum write.cafe submissions, email verification, anything? Recommendation: leave open, watch for abuse, gate only if quality drops.
- Editor display name attribution: the creator sees the editor's display name verbatim (same handle pattern used for tip attribution, journal byline, etc.). Should pen-name attribution be allowed even in v0? Recommendation: defer to T2 — pen-name UX has tradeoffs that deserve their own pass.
- No revoke after delivery: delivered critiques are append-only (matches
story_critiques). If an editor delivers a bad-faith critique we'd handle it via the same flagging path other content uses, not by retroactive deletion.
T0.6 — AI assistance for human editors (shipping)
Editors working a claimed critique get a small AI sidebar inside the deliver view: angle-pill buttons that pull a focused 150–300-word take from a fresh persona for the editor to use as a starting point. The pulled takes stack vertically with clear "AI starting point" labels. The editor's own textarea is unchanged — these are reference material, never auto-filled.
Six angles, each with a baked-in editorial directive in the system prompt so the take is focused, not a generic "what do you think":
| Angle | Icon | Directive snapshot |
|---|---|---|
| Pacing | 🎚 | Where does the chapter speed up / slow down? Where does it linger or rush? |
| Voice | 🎤 | Is the narrator's voice consistent and personal, or generic-novelist? |
| Dialogue | 💬 | Do characters sound distinct? Does dialogue reveal character or just info? |
| Structure | 🏗 | Does the chapter open + end in the right place? Scenes overstay or beats missing? |
| Hook | 🪝 | First-paragraph reason-to-keep-reading? Last-paragraph reason-to-want-chapter-N+1? |
| Specificity | 👁 | Are details concrete and earned, or generic ("the room was dark")? |
Pieces
services/editor-assistance.ts—generateEditorAssistance({ storyTitle, storyBody, angle, usedPersonaIds, callerUserId }). Picks a fresh persona (excludes ones the frontend has already used this session), runs gpt-4o-mini with the angle directive baked into the system prompt, returns the body. No DB insert — pure reference material. Spend rides oncomplete()withfeature_key='editor_assistance.angle'.POST /api/critique-requests/:id/assist— body{ angle, used_persona_ids?: string[] }. Auth: only the user who claimed the request can call. Returns{ assist: { persona_id, persona_display_name, persona_mbti, angle, body } }./write-cafe/critique-queuedeliver view — angle-pill row above the critique textarea, stacked takes below the pills (each labeled with persona name + angle), client-sideused_persona_idstracking so successive clicks rotate fresh voices.
Design notes
- Why preserve persona attribution. Reading "Maria the Editor noticed an awkward pivot in the middle act" is more inspiring (and more useful for the editor's own voice calibration) than reading a flat "Pacing angle says X." Same
ai_personaspool that powers T0, no separate "assistant" personas. - Why no auto-fill. The whole point of T0.5 is human critique. If the textarea auto-filled with an AI take, the editor would be a copy-paste plagiarist. The takes stack ABOVE the textarea as visible reference; the editor types their own words below.
- Why no separate assistance log. Spend already tracks via
feature_key. If we want per-editor caps later, we can add a count table; for v0 the LLM dashboard is the audit trail.
Open product questions for T0.6
- Rate limit? Right now an editor can pull all 6 angles back-to-back, then pull all 6 again with fresh personas. At ~$0.0008/call that's ~$0.01 / fully-explored chapter. Probably fine. Cap if we see someone burning cycles on chapters they never deliver.
- "Polish my draft" mode — letting the editor paste their own draft and asking an AI to suggest revisions. Powerful but veers from "AI assists editor" into "AI co-authors the critique." Recommendation: defer. The angle takes are the inspiring move; polish is the copyediting move and risks diluting the editor's voice.
- Line-level annotations — AI marks up specific lines of the prose with inline notes. Way more UI work + interaction design. Defer to a later pass.
- Show prior critiques on the same chapter in a small sidebar so the editor sees what's already been said (both AI personas from T0 and any prior human deliveries). Useful context but adds a fetch + complexity. Recommendation: ship without it for v0, add if editors ask.
T2 — Human editor marketplace
Open up story_critiques as a destination for real editors — paid critiques from humans, alongside the AI personas. Same panel on the creator side; rows just have a different source attribution.
Phase split (the original "1 monolithic ~6-PR build" estimate was too big to land in one swoop — splitting so each phase is independently shippable + reviewable):
| Phase | Scope | Status |
|---|---|---|
| T2.1 | is_editor flag, editor_profiles table, application form, super-admin review console | ✅ shipping |
| T2.2 | Public marketplace at /editors, commission booking flow + lifecycle, editor's incoming queue with accept / decline / deliver | ✅ shipping |
| T2.3 | Stripe Connect Express onboarding, escrow capture, payout dashboard | TODO |
| T2.4 | Delivery review + creator confirmation + 7-day auto-release | TODO |
| T2.5 | Public ratings, /editors/{handle} profile page, testimonial wall | TODO |
| T2.6 | Strike system + super-admin dispute review console | TODO |
T2.1 — Application + approval foundation (shipping)
The bare minimum that lets editors raise their hand + super-admins curate the marketplace's signal before Phase 2 wires up booking + payments.
What landed:
- Migration 340 —
profiles.is_editor(boolean default false) +editor_profilestable. The profile carries lifecycle (pending → approved | rejected → suspended), self-edits (specialties, rate_per_chapter_cents, turnaround_days, accepting_work, sample_critique, bio, display_name_override), and review fields (status_changed_at / by, review_note). Partial indexes for the FIFO pending queue + marketplace browse hot path. services/editor-profiles.ts—submitEditorApplication(one row per user, normalized specialties),getMyEditorProfile,updateMyEditorProfile(self-editable fields only — status is super-admin),listEditorApplications(FIFO for pending, recency for everything else),setEditorStatus(super-admin gated; syncsprofiles.is_editorbased on the new status),listApprovedEditors(forward-compat — T2.2 marketplace consumes this).routes/editor.ts— mounted at/api/editor. Endpoints:POST /apply,GET /profile,PATCH /profile,GET /applications?status=X(super-admin),POST /applications/:id/decide(super-admin; one endpoint for approve / reject / suspend)./editor/applypage — single-form: specialty multi-select (1–5), rate per chapter, turnaround days, optional pen name + bio, sample critique (200–8000 chars). On submit → routes to/editor. If a prior application exists (any status), pre-routes to/editorinstead of letting the user double-submit./editorpage — minimal status surface. Pending = "super-admin will review." Approved = "marketplace ships in Phase 2." Rejected = surfaces the reviewer's note. Suspended = "contact support."/dashboard/admin/editor-applicationspage — super-admin review console. Tabs by status (pending / approved / rejected / suspended). Click "Open" on a row → expands to show the sample critique full-text + a decision-note textarea + Approve / Reject / Suspend buttons. Approve flipsprofiles.is_editor=true. Other decisions untouch the flag.
Why this is the right Phase 1:
- Builds the supply side first so when Phase 2 ships the marketplace browse, there are real approved editors to populate it.
- Establishes the trust signal (super-admin approval gate) before any money moves. We're not in a hurry to start charging — Phase 1 is "let editors apply + curate."
- Tables are forward-compatible. T2.2 just adds
editor_commissions+ extendsstory_critiques; the existingeditor_profilesshape doesn't need to change.
Intentionally simple in T2.1:
- No editor profile editing post-approval. The PATCH endpoint exists, but the dashboard doesn't render the edit form yet — that ships alongside the marketplace browse in T2.2 (no point editing your rate until creators can find you).
- No email notification on decision. Editors check
/editorto see their status. Hook into the existing email system in a follow-up. - No application re-submission UX. A rejected editor can't apply again from the UI; needs super-admin intervention. Avoids the "auto-resubmit spam" loop; we'll add a 30-day cooldown to UI once we see the volume.
T2.2 — Marketplace browse + commission booking (shipping)
The supply side now has a home + demand side. Creators can find approved editors, commission a specific one for a specific chapter, and the editor sees the request and responds. Everything happens through the existing chapter critiques panel + a new top-level marketplace page; v0 stays free on both sides so we can stress-test the matchmaking surface without dragging Stripe in.
What landed:
- Migration 341 —
editor_commissionstable (lifecycle:pending → accepted | declined,accepted → delivered | cancelled).price_centsis forward-compat for T2.3 — captured as a snapshot of the editor's currentrate_per_chapter_centsat booking time so the creator's confirmed price doesn't move if the editor raises their rate mid-flight. Plus partial indexes for the editor active queue, creator's outstanding list, and a per-target lookup so the chapter panel can show "you've already commissioned someone here" without scanning the table. Also extendsstory_critiqueswith the long-deferrededitor_user_idandcommission_idnullable FKs (a row's source is now unambiguous:persona_id IS NOT NULL→ AI;editor_user_id IS NOT NULL→ human). services/editor-commissions.ts—createCommission(rate snapshot + ownership pre-check upstream + one-in-flight-per-chapter guard),listIncomingForEditor(pending + accepted, newest first),listOutgoingForCreator(any status),acceptCommission(atomicpending → accepted),declineCommission,cancelCommission,deliverCommission(inserts astory_critiquesrow witheditor_user_id+commission_idstamped, then atomically flips the commission to delivered and linksdelivered_critique_id).routes/editor.ts— extends the T2.1 router withGET /marketplace?specialty=X(approved + accepting; decorated with display name + handle),POST /commissions(creator ownership check on the chapter),GET /commissions/incoming(editor's queue, decorated with chapter context + creator display name),GET /commissions/outgoing(creator's list), andPOST /commissions/:id/{accept,decline,cancel,deliver}./editorspage — public marketplace browse. Filter chips by specialty (same 7-angle vocab as T1.1 / T1.3). Cards show display name, pen-name disclosure when applicable, specialties, rate, turnaround, bio. Sort defaults to rate ascending — best-fit-first per the roadmap's anti-race-to-the-bottom note; the page can re-sort.EditorPickerModal+ChapterCritiquesPanelintegration — a new "🤝 Commission an editor" button on each chapter row opens a modal with the same marketplace list + specialty filter, a note-to-editor textarea (1000 char cap), and a confirm step that shows the editor's full card before the creator pulls the trigger. While a commission is in-flight on a chapter (pending or accepted), the button swaps to a status chip ("🕊 Commission sent" or "🤝 Editor working") so the creator can't double-book./editordashboard — extended with an "Incoming commissions" section for approved editors. Each row shows the show + chapter, the creator's note, an expandable chapter excerpt + deep-link to the reader, and per-status actions: pending → Accept / Decline (with optional reason); accepted → Deliver (textarea, 50-8000 char body, posts toPOST /:id/deliver). On deliver, the row drops out of incoming (terminal) and astory_critiquesrow lands on the creator's panel attributed to the editor's pen-name-or-display-name.
Why this is the right Phase 2:
- Closes the loop end-to-end without payments. v0 is free; the entire creator → editor → delivery → critique-attribution loop works. T2.3 wires Stripe Connect on top without changing the existing lifecycle.
/editorsis a shopfront, not a directory. Booking happens from the chapter panel, not from the marketplace browse — the marketplace exists to help creators find the right editor, then the actual commission flow stays in context.- Forward-compat with Stripe.
price_centsis already on the row, snapshotted at booking. T2.3 just adds Stripe fields (payment_intent_id, captured_at, refunded_at, payout_id) and gates the lifecycle transitions on capture state. Existing free rows stay valid because they're not charged.
Intentionally simple in T2.2:
- No
/editors/{handle}profile page yet. Cards in the marketplace are enough for v0. The profile page lands in T2.5 with public ratings. - No editor profile self-edit form. Rate / turnaround / availability stays super-admin-only via the PATCH endpoint until T2.3, so we don't have to handle "editor raised rate while a commission was pending" edge cases.
- No notifications. Editors check
/editorfor incoming commissions; creators check their chapter panel for delivered critiques. Email + in-app notifications layer in once we see the surface in real use. - Free-form sort. No "best match for my specialty" or "fastest turnaround" filters yet — rate-ascending is the default. We'll layer real ranking once we have N>20 approved editors.
Editor side
is_editorcapability flag onprofiles— opt-in by application + sample submission. Mirrorsis_creator.- Editor profile page at
/editors/{handle}— specialties (same vocabulary as T1), sample critique excerpt, hourly or per-chapter rate, stated turnaround time, optional "currently accepting work" toggle. - Editor dashboard at
/editor— queue of commissioned chapters, deadline tracking, payout history.
Creator side
- New "🧑🏫 Commission a human editor" button next to the AI persona buttons on
ChapterCritiquesPanel. - Marketplace page at
/editors— filter by specialty + price + turnaround. Booking flow with up-front cost shown. - Delivered editor critiques appear in the same
ChapterCritiquesPanel(and the/studio/inboxif we've built that by then), badged differently from AI ones.
Schema work
- Extend
story_critiques:- Add
editor_user_id uuid REFERENCES profiles(id) ON DELETE SET NULL(nullable). - Add
commission_id uuid REFERENCES editor_commissions(id) ON DELETE SET NULL(nullable; links to the payment record). - Adjust render: a row has either
persona_id(AI) oreditor_user_id(human), never both.
- Add
- New tables:
editor_profiles— specialties, rates, sample, availability.editor_commissions— creator → editor → target chapter, price, status (pending, accepted, delivered, accepted-by-creator, disputed, refunded), Stripe payment intent id, escrow release timestamp.editor_reviews— creator's rating + optional public testimonial after a commission lands.
Payments
- Stripe Connect (Express accounts) for editor payouts — same pattern as the tipping rails we already use for writer tips. Each editor onboards a Connect account from
/editor/onboarding. - Escrow via delayed capture — creator's card is authorized at commission time, captured on delivery. Auto-released 7 days after delivery if the creator doesn't dispute.
- Platform fee: 15% (configurable per migration; matches typical creator-economy marketplaces — Substack 10%, Patreon 8%, Gumroad 10%, Twitch 50%, Bandcamp 10–15%). 15% gives us margin for support + Stripe fees (~2.9%) + dispute handling.
Quality control
- Application gate — anyone-can-apply, super-admin approves. Sample critique on a fixed prompt is part of the application. Reduces signal noise in the marketplace early.
- Public ratings after each commission, with optional written testimonial. Average shown on editor profile.
- Editor strike system — 3 disputes lost in a 30-day rolling window → automatic suspension pending super-admin review.
- Refund-as-needed — super-admin can refund a commission (returns the capture, debits the editor's pending balance) without dispute. Keeps the platform from being seen as adversarial to creators.
Open design questions
- Specialty vs. open pricing — editors set their own price within platform-set tiers (Pro $40 / Veteran $80 / Specialist $150), or free-pricing? Tier-bound is easier to shop; free-priced is more capitalist and lets veterans charge what they're worth. Recommendation: free-pricing, with the marketplace UI defaulting to a "sort by best fit, not cheapest" sort — and the editor profile page surfacing the all-in cost prominently so creators don't get surprised.
- Sample-critique gate — required at application time? Or just "submit your first 3 critiques at zero pay, super-admin reviews before you can charge"? Recommendation: required sample at application; faster than a 3-paid-zero ramp and less awkward for new editors.
- Anonymity — should editors be able to optionally critique under a pen name? Most "named editor" marketplaces (Reedsy, Bluepencil) keep real names; we could allow a pen-name option for niche genres where reputation tradeoffs differ. Recommendation: real names default, pen-name allowed but disclosed in profile chrome (so the creator knows they're booking a pen-name'd editor).
- Re-revision flow — editor delivers a critique, creator revises the chapter, can they ask the same editor to re-review for a discount? Recommendation: yes — "Same editor, second look — 50% off" button after a first critique lands. Better LTV for the editor + creator both.
- Cross-product source — when a write.cafe writer asks for a paid critique on their cafe submission, does that ride the same marketplace? Probably yes — the cafe writer is the same customer; the editor doesn't care which surface the prose came from. Polymorphic
target_kindalready supports this.
Estimated build: ~4–6 PRs
- Schema + editor profiles — migration,
editor_profilestable,/editor/onboardingpage. ~1 day. - Marketplace browse + commission booking —
/editorspage, filter + sort, booking modal with cost preview. ~1.5 days. - Payment rails — Stripe Connect onboarding, escrow capture, payout dashboard on
/editor. ~2 days. - Delivery flow + creator review — editor's deliver-critique form (same
story_critiquesinsert path but witheditor_user_idset), creator's accept/dispute UI, 7-day auto-release cron. ~1 day. - Public profile + ratings —
/editors/{handle}with average rating, testimonial wall. ~0.5 day. - Strike system + super-admin dispute review console — internal-only admin chrome. ~1 day.
When to build what
- T0 → T1: wait until T0 has at least 10 distinct shows that requested a critique. If usage is < 10/show across 30 days, the persona suite won't justify the build; T0's "random fresh persona" already covers the case where users are curious-only.
- T1 → T2: wait until 5+ creators ask for human editing in feedback, OR T1 hits its first "I'd pay more for a real person" qualified moment. The marketplace's operational weight (payouts, dispute support, quality control) only pays off when there's real demand on both sides. Don't preempt.
Things to check before T1 ships
ai_personas.is_activefilter on the browse — make sure the curated set isn't muddied by experimental personas the admin has been testing for journal-mode.- A persona's stated
voice+core_tensionshould be visible in the browse modal so the creator knows what they're picking. Today they're only fed into the LLM prompt, not surfaced in UI. - The "fresh persona" rotation logic in
story-critiques.tsalready prevents re-pulls for the same target — good. When T1 lets the creator pick, we should still show "you've already had this critic on this chapter" in the modal so they don't accidentally double up.
Things to check before T2 ships
- Stripe Connect availability in the editor's country — Connect Express has a country list narrower than the US-only Stripe Standard. Need a clear "not available in your country yet" path.
- Tax handling — 1099-K threshold for US editors is $5k/year now (post-2024); we'll need to surface earned-to-date on the editor dashboard so they can plan.
- KYC requirements when an editor crosses the platform threshold (~$1k/year in some regions) — Connect handles this but we need a UI flow for the prompt.
Document owner: when this file gets stale (>3 months without an edit), strike it through and replace. Roadmaps that quietly rot are worse than no roadmap.