UAT β editor workflow (Phase 6β10 acceptance)
Test account: rp.editor@example.com (approved editor, Wakecut,
magic-link). Site: https://wakecut.com
Runs opposite docs/ops/UAT-CLIENT-WALKTHROUGH.md (client =
dorian@redpoint-vision.com) and the ops console (admin =
man_on_rock@redpoint-vision.com). Use separate browser profiles.
The promises this flow must keep: pay/brief visible before you commit, no client contact ever, payouts per job via Stripe with nothing blocked while you set them up.
π§± Phase-10 migration prerequisite. The new sections (Β§2b held jobs, Β§3b voice notes) and the operator notes (Β§8) depend on migrations
0034β0039, which the shared dev/prod DB was last verified below (at0033). Until the operator applies them, the surfaces probe-and-skip (stay dark) by design. Confirm the live level first (rule 8: probe the object, don't trust docs).
π§ Editor emails (Phase 7) β verify via the notifications table, not an inbox. The editor test account is
rp.editor@example.com, a non-deliverable@example.comaddress, so editor-facing emails (job_assigned,revision_requested,qc_rejected,payout_paid) won't land in a real inbox. Verify them as the admin (man_on_rock@β¦): thenotificationstable is admin-readable β one row per event,status = sent(orsendingβsent),recipient= the editor address, uniquededupe_key. Substitute a real editor inbox if you want to eyeball the rendered mail. RequiresRESEND_API_KEYin the Vercel prod env (rotate the chat-transited keys first); migration0020is already live.
1 Β· Payout setup (Stripe Connect β new)
| # | Do | Expect |
|---|---|---|
| 1.1 | Log in β /workspace |
Banner: "Payouts run on Stripe. Set up once, get paid per job." β informational, nothing else is blocked |
| 1.2 | "Set up payouts" | Redirects to Stripe-HOSTED onboarding (test mode: fake data is fine, use test SSN/IBAN prompts as offered) |
| 1.3 | Abandon halfway, return to /workspace |
Banner switches to "Almost there β Stripe needs one more detail" + "Finish setup"; you can still claim/work jobs |
| 1.4 | Finish onboarding | Banner gone; quiet "Payouts enabled" chip in the header (may take a moment β webhook-driven) |
2 Β· Job board (claim)
| # | Do | Expect |
|---|---|---|
| 2.1 | /board |
Pool jobs as cards: pay, deadline, and brief visible BEFORE accepting. No client name/identity |
| 2.2 | Tap Pass on a card | Reveals optional pass-reason chips β "Timeframe too tight" / "Scope too big" / "Footage quality" / "Not my skill set" / "Other" β plus "Pass without a reason". Copy is tenant-config (Phase 6b slice 3), values unchanged. Either way the card leaves the board and stays gone on reload |
| 2.2a | If any card carries a boost badge | Label reads "Boosted pay" (not "Rescued"/"Boosted"); the pay figure is the real boosted amount |
| 2.3 | Claim the UAT job (the one dorian created) | Instant assign; deadline auto-set from the package turnaround; job appears in /workspace. No assignment email β a board self-claim deliberately doesn't email (only an admin assigning you fires "New job assigned"; check the notifications table shows no job_assigned row for this self-claim) |
| 2.4 | (If a second editor account is handy) try claiming the same job simultaneously | Exactly one wins; the loser gets a clean conflict message, not an error dump |
2b Β· Held jobs β specify-editor continuity (0038)
Precondition (βοΈ + operator setup): a client (dorian@β¦) placed a
specify-editor order naming this editor's pseudonym (client sheet Β§1b) β
dorian@β¦ must have a prior delivered edit from rp.editor@β¦ for the
pseudonym to be offerable. Migration 0038 live; the hold window still open
(tenant billing_config.specify_editor_hold_hours, default 48h).
| # | Do | Expect |
|---|---|---|
| 2b.1 | On /board as rp.editor@β¦, find the held job |
It carries a "reserved for you" chip (hover: "A repeat client asked to work with you again β reserved for you for a limited window before it opens to the pool.") and sorts to the top (held-for-me first). Still no client identity |
| 2b.2 | During the hold window, from another editor account | The held job is NOT visible to other editors β only you see it until the window expires (defence-in-depth: a hand-crafted claim by anyone else is rejected, "this job is reserved for another editor right now") |
| 2b.3 | Claim it | Instant assign β you still claim it yourself, exactly like any board job; it is never auto-assigned |
| 2b.4 | If you do NOT claim before the window ends | The job simply opens to the whole pool (nothing on your side changes); the client's add-on auto-refunds client-side β you see nothing about that money |
No notification tells you a job is held for you β no email, no push. The only signal is the board chip, so an editor learns of a reserved job only by opening
/board. That is deliberate; note it, don't log it as a missing email.
3 Β· Work the job
| # | Do | Expect |
|---|---|---|
| 3.1 | Open the job in /workspace |
Full brief rendered, client footage downloadable one-click, client identity limited to what the brief needs |
| 3.2 | "Start work" | Status ASSIGNED β IN_PROGRESS |
| 3.3 | Upload panel | Three distinct paths: draft / watermarked preview / final (new). Upload one of each (the "preview" should be a watermarked export; final = clean) |
| 3.4 | Submit for QC | Status β IN_REVIEW; βοΈ admin QC-passes in /ops/jobs/[id] |
| 3.4π§ | βοΈ If admin QC-rejects instead (with a note) | Editor gets "Changes needed before review (β¦)" carrying the QC note verbatim; a second, differently-worded rejection sends a SECOND email (keyed by occurrence, not revision count). Verify via the notifications table |
| 3.5 | If the client requests changes | The revision note arrives as written instructions on the job; rework β resubmit |
| 3.5π§ | On that revision request | Editor gets "Revision requested (β¦)" with the client's note relayed verbatim β and NOTHING else about the client (no name, no company, no other brief text). Verify via the notifications table |
3b Β· Client voice notes in your workspace (0034)
Precondition: dorian@β¦ recorded β₯1 voice note (client sheet Β§2b);
migration 0034 live; you are the assigned editor. Editor-facing playback is
double-blind β audio only, no client identity, no transcript.
| # | Do | Expect |
|---|---|---|
| 3b.1 | Open the assigned job in /workspace |
A "Client voice note(s)" section: the newest note plays prominently, captioned "The client's spoken brief for this job." (single) or "The client's LATEST spoken brief (newest of N)." (many) |
| 3b.2 | If more than one note exists | An "Earlier notes (Nβ1)" disclosure holds the older notes, each with its own player β full history kept, nothing overwritten |
| 3b.3π§ | When the client adds a new note | You get "New brief voice note (β¦)" β email and push ("The client added a voice note to the brief.", CTA "Open the job"). rp.editor@β¦ is @example.com, so verify via the notifications table (one row, unique dedupe_key per note). Dedupe is per note, so each new note notifies afresh. No client identity in the mail |
| 3b.4 | If a note was added before any editor was assigned | No email fires (no recipient yet) β you simply see it in the workspace when you pick up the job |
4 Β· Getting paid (after the client approves)
| # | Do | Expect |
|---|---|---|
| 4.1 | After βοΈ client approval: open the job in /workspace |
Payout card: amount in Β£ (from the package's editor split), state chip Pending β Sent with timestamp |
| 4.1π§ | When the transfer actually succeeds | Editor gets "You've been paid (β¦)" β fires ONLY on a confirmed transfer (never on pending/failed), amount in GBP. If a payout first fails then succeeds on admin retry, still exactly ONE email (shared dedupe key). Verify via the notifications table |
| 4.2 | Stripe test dashboard β Connect β your test account | The transfer exists, amount matches the payout card |
| 4.3 | If a payout ever shows "Retrying" (amber) | Copy says it's the platform's problem, NO action button for you β verify nothing asks the editor to fix money |
| 4.4 | Throughout: confirm payout state never blocked claiming or working other jobs | Board + workspace fully usable at every payout state |
5 Β· Boundary checks
- Editor must NEVER see: client email/company beyond the brief, other
editors' jobs (
/workspaceshows only yours), other editors' payouts, the client's payment amount (only YOUR payout). - Editor emails carry no client identity (Phase 7 double-blind): the only
client-authored text that reaches an editor email is a relayed revision/QC
note; no name, company, vessel, location, or brief body ever appears. Confirm
against the
notificationsrows / rendered mail. /opsas the editor β redirected away (admin-only).- The job you dismissed on the board must not reappear.
- Open
/editors(recruitment page): the FAQ now includes "What if a job isn't right for me?" β "pass freely, no penalty; telling us why makes jobs better for everyone." Honest framing, no invented penalty/number.
6 Β· iOS app (installable editor app β 2026-07-11)
Prerequisite: same as the client sheet's Β§6 β {{ .Token }} in the
Supabase email template. Device: a real iPhone, Safari.
| # | Do | Expect |
|---|---|---|
| 6.1 | Safari β /workspace (logged in as rp.editor@β¦) |
Install hint appears; Share β Add to Home Screen yields a SECOND, distinct app β engine-dark identity, NOT named/coloured like the client "Wakecut" app; opens straight on the workspace |
| 6.2 | Both apps on the home screen side by side | Two clearly different icons/names β installing one never replaced or restyled the other; hint dismissals are independent per app |
| 6.3 | In the INSTALLED editor app: sign out, then sign back in via the emailed code (not the link) | Signed in inside the app, landed on the workspace |
| 6.4 | Run the core loop in the app: board card (pay/deadline/brief visible) β claim β open job β download footage β upload a draft | All usable at phone width in standalone mode; uploads run in the foreground (backgrounding the app mid-upload may pause it β resume on return is acceptable; note what you see) |
| 6.5 | Payout card (Β§4) inside the app | Renders correctly; Stripe Connect onboarding, if launched, completes in its hosted flow and returns |
Pass bar (Β§6): two independent apps with the right identities, code-based sign-in works entirely in-app, and the Β§1βΒ§4 promises hold in standalone mode.
Pass bar: pay-first board holds, all three upload kinds work, the payout card matches Stripe reality, nothing money-related ever blocks or blames the editor, and every editor email (Β§3.4π§/Β§3.5π§/Β§4.1π§) lands once with the client's identity absent.
Phase-10 pass bar (Β§2b/Β§3b/Β§8): a specify-editor job is reserved to you (and hidden from others) for the hold window, claimable only by you, with no notification beyond the board chip; client voice notes play newest-first with history kept and fire one "New brief voice note" alert per note, carrying no client identity; and the board stays byte-identical to Β§2 because supervision is dormant (1E ops UI pending). All double-blind holds.
7 Β· Operator notes β Phase 7 crons (admin/system, not an editor click-path)
The two scheduled sweeps are secret-gated and run on Vercel Cron, so they aren't part of the click-through above. Verify separately:
CRON_SECRETmust be set in the Vercel prod env or both crons are dead (the routes 401 without a matchingAuthorization: Bearer). Cadence:sla-checkruns daily (0 8 * * *β hourly was rejected by the current Vercel plan; restore hourly on Pro),retainer-nudgeat 09:00.- SLA breach β when a job's internal deadline passes while the editor still
owes work, each admin (
profiles.role = 'admin') gets one "SLA breach β job β¦ overdue" email per breached deadline (a reassignment sets a new deadline β a fresh alert; an overdue job never re-spams across runs).man_on_rock@β¦is a real inbox. To force it without waiting a day, assign a job a past internal deadline (admin) andcurlthe route with the Bearer secret. - Retainer nudge is OFF and must stay off. It only fires when a tenant sets
billing_config.retainer_nudge_hours(Wakecut: unset β verified). Do not enable it until an opt-out/unsubscribe path exists (ruling 9 β marketing- adjacent mail; the template currently has noList-Unsubscribe). Flagged as an operator precondition, not a UAT step.
8 Β· Operator notes β Phase 10 (editor supervision + partner commissions)
Both are admin/system surfaces, not an editor click-path. Verify separately.
Editor supervision (0039) β DORMANT by default
- The board is UNCHANGED unless supervision is switched on. Supervision only
acts when
tenants.dispatch_config.supervision.enabled = true(default false), and even thenauto_actionsdefaults false (transitions queue for a human, they don't auto-apply). With it off, a good / unscored / no-row editor sees a byte-identical board and dispatch β Β§2's steps behave exactly as before. Do not expect any standing/throttle/suspend behaviour on the board out of the box. - The ops roster / confirm-queue UI ("1E") is NOT merged (PR #80, in
review). So there is no operator surface yet to view editor standings or
confirm/reject queued actions β those UAT steps are PENDING 1E. The standing + append-only
audit tables and the decision engine exist (migration
0039), reachable only via the per-rating hook and thesupervisioncron sweep β but nothing operator-clickable. - When 1E lands + supervision is enabled (future UAT): a suspended (editor, tenant) sees none of that tenant's board; a throttled one has a reduced concurrent cap and their jobs sink below good-standing work; warn is informational (no dispatch teeth). All per-(editor, tenant). PENDING 1E.
Partner commissions dry-run (0035/0036) β ops β finance
- As admin (
man_on_rock@β¦), open/opsβ finance β commissions (/ops/finance/commissions). Heading "Partner commissions", subtitle "Dry-run accruals β review before arming". Read-only: no money moves from this page. - Arming state shows env
PARTNER_COMMISSION_ENABLEDas "off (dry-run)" (or "ARMED"); a real transfer needs both that env and a tenant'scommission_liveon β both default off, so on merge the leg records numbers and moves zero money. - First-party tenants accrue NOTHING. Wakecut (and any tenant with no partner Connect account) is the merchant, keeps everything, and is not even listed as a partner β the capture path is byte-identical. Only partner (seat) tenants with a Connect destination appear.
- "fee unset" chip on a partner row = that tenant has no
platform_fee_bps(tooltip: "No platform_fee_bps set β Redpoint's full cut accrues to the partner (accrual uses 0)"). FX-held banner flags accruals where a non-GBP (EUR) gross was netted against a GBP fare β these are HELD (never auto-transfer, even when armed) pending a real EURβGBP conversion. - If migration
0035isn't applied, the page shows a "Migration 0035 is not appliedβ¦" notice and records no accruals β apply it, then reload.