12 — Moderation Settings (Screen 9, `/app/testimonials/moderation`)
Cursor-ready plan: shop-level auto-moderation rules, blocklist, video length limits, team notifications, and where to enforce them in the pipeline.
12 — Moderation Settings (Screen 9)
Section titled “12 — Moderation Settings (Screen 9)”Source: 02-Implementation-Blueprint.md — Screen 9 - Moderation Settings (/app/testimonials/moderation).
Product alignment: 01-Post-Purchase-Video-Testimonial-Collector-Plan.md — §6 (moderation), §7 (Phase 2+ AI signals — only placeholder UI here for blur detection).
This document is a build spec only. No code changes are implied until a task references this file.
Related: 06-public-submission-page-screen-13.md (apply rules on submit), 09-submissions-inbox-screen-5.md and 10-submission-detail-screen-6.md (human review), 08-security-compliance-and-privacy.md (PII in blocklist / emails).
0) Goal (one sentence)
Section titled “0) Goal (one sentence)”Merchants configure automation and guardrails (auto-approve photos, minimum rating, blocklist, video duration bounds, team notification recipients, and a disabled future toggle for AI blur) in one admin screen; the backend enforces these rules at submission time and/or when new pending items arrive.
1) Route and file
Section titled “1) Route and file”| Item | Value |
|---|---|
| Suggested file | app/routes/app.testimonial-moderation.jsx (per blueprint §6). |
| URL | /app/testimonials/moderation. |
Auth: authenticate.admin(request); read/write only the current shop’s settings.
Nav: add under testimonial group, e.g. “Moderation” or “Moderation rules”.
2) Data storage (shop-scoped)
Section titled “2) Data storage (shop-scoped)”Option A (simplest): add nullable columns to Shop (or a dedicated TestimonialModerationSettings model with shopId @unique if you want a slimmer shops table).
| Blueprint field | Suggested column / type | Notes |
|---|---|---|
| Auto-approve photos only | testimonialAutoApprovePhotos Boolean @default(false) | Already named in blueprint §5.1 — reuse or align. |
| Require minimum rating | testimonialMinRating Int? | null = no minimum; 1–5 when set. |
| Blocklist words | testimonialBlocklistWords String? @db.Text | Store as newline-separated or JSON array; normalize to lowercase for matching. |
| Minimum video length (seconds) | testimonialMinVideoLengthSec Int? | null = no minimum. |
| Maximum video length (seconds) | testimonialMaxVideoLengthSec Int? | null = use hard platform cap elsewhere. |
| Reject blurry (future AI) | testimonialBlurRejectionEnabled Boolean @default(false) | UI: disabled with “Coming soon” — no worker in v1. |
| Team notification emails | testimonialTeamNotifyEmails String? @db.Text | Comma- or newline-separated list; validate on save. |
Migration: one Prisma migration; backfill is not required (nulls = “not configured”).
3) Admin UI (Polaris)
Section titled “3) Admin UI (Polaris)”3.1 Page structure
Section titled “3.1 Page structure”- Page
title="Moderation settings" - Layout:
FormLayoutorBlockStackwith Cards:- Automation — auto-approve photos, min rating
- Content safety — blocklist
- Video rules — min/max length; blur toggle (disabled)
- Notifications — team emails
3.2 Field-level UX
Section titled “3.2 Field-level UX”- Auto-approve photos only —
Checkboxwith help text: “Photo submissions skip the pending queue and are marked approved. Video always requires review unless you add more rules later.” - Require minimum rating —
Select1–5 or “No minimum” (null). - Blocklist —
TextFieldmultiline={5}; help: “One word or phrase per line. Submissions containing these (case-insensitive) are rejected or held—define behavior in §4.” - Min / max video length —
TextFieldtype="number"withmin={0}; show error ifmin > maxwhen both set. - Blur rejection —
Checkboxdisabled +TooltiporBanner“Planned: AI quality checks.” - Team notification emails —
TextFieldmultiline; validate each token as email; max N addresses (e.g. 10) to avoid abuse.
3.3 Save
Section titled “3.3 Save”- Primary action “Save” →
actionwith intentsave_moderation_settings. - Success / error toasts; use
useSubmit+useActionDataor Remix patterns.
4) Enforcement rules (where to implement)
Section titled “4) Enforcement rules (where to implement)”Document this in code comments so 06 and any async workers stay consistent.
4.1 On public submit (api.testimonial-submit — 06)
Section titled “4.1 On public submit (api.testimonial-submit — 06)”After creating TestimonialSubmission in pending (or before final commit), run validation:
-
Blocklist — if
headlineorreviewTextcontains any blocklist entry (word boundary or substring — choose one; substring is simpler but more false positives):- Option A: set
status=rejectedwith system reason +TestimonialModerationLogactorType=system. - Option B: keep
pendingbut setflagged/holdfor merchant — pick A or B and document (A reduces inbox noise; B is safer for false positives).
- Option A: set
-
Minimum rating — if
testimonialMinRatingis set andrating < min(orratingnull when required): 400 with clear message or create rejected row per product policy. -
Auto-approve photos — if
mediaType === 'photo'andtestimonialAutoApprovePhotos === true:- Set
status=approvedand optionallypublished=false(publish still from 10 / 11 if you separate concerns) orapproved+published=truefor fastest go-live — pick one; recommended: approved only, publish via 11.
- Set
-
Video length — compare
TestimonialMediaAsset.durationSec(must be populated after upload metadata extraction — if still null at submit, queue validation in media worker or reject with “processing” state). If duration out of bounds: reject or hold per same policy as blocklist.
4.2 Blur / AI (v1)
Section titled “4.2 Blur / AI (v1)”- No-op. If toggle is disabled in DB, skip. When implemented later, hook in media callback worker.
4.3 Team notifications
Section titled “4.3 Team notifications”v1 minimum: on new TestimonialSubmission with status=pending (and not auto-rejected), send one email to testimonialTeamNotifyEmails using app/lib/email.server.js (Brevo), subject line includes shop name + submission id.
Idempotency: do not email on duplicate submit (409).
Optional: digest mode — out of scope for Screen 9 v1.
5) Integration with inbox (09)
Section titled “5) Integration with inbox (09)”- Auto-rejected rows may not appear in Pending tab if you set
status=rejectedimmediately — they should appear under Rejected with reason “Blocklist” or “Below minimum rating.” - Auto-approved photos appear under Approved (and optionally Published after merchant action).
6) Security and privacy (08)
Section titled “6) Security and privacy (08)”- Blocklist should not log matched substrings if they contain PII — log
submissionId+ rule id only. - Team emails are merchant-only; never expose on public API.
7) Testing checklist (acceptance criteria)
Section titled “7) Testing checklist (acceptance criteria)”- Save invalid email list → validation error, no partial save.
-
min > maxvideo bounds → validation error. - Submit with blocklisted word → expected reject or hold per §4.1.
- Photo + auto-approve on → submission is approved (and log says
systemif you log auto-approve). - Video length out of range → rejected or held.
- Team email sends (or is skipped if list empty) when a new pending submission is created.
- Blur toggle remains disabled in UI; no server path depends on it.
8) Implementation order (for Cursor)
Section titled “8) Implementation order (for Cursor)”- Prisma: add/merge columns on
Shop(or new 1:1 settings model). app.testimonial-moderation.jsxloader + action + Polaris form.- Extract
applyModerationRulesToSubmission({ shop, submission, text, media })inapp/lib/testimonial-moderation.server.js(name flexible). - Call from
api.testimonial-submitafter create (or inside transaction). - Add team notify email in same request or deferred job (queue table optional for v1).
- Update 09 empty copy if all auto-handled.
9) References
Section titled “9) References”02-Implementation-Blueprint.md— Screen 9, §5.1 (testimonialAutoApprovePhotos,testimonialConsentVersionlives on Shop; moderation fields can live alongside).01-Post-Purchase-Video-Testimonial-Collector-Plan.md— §6–7 moderation and future AI.06-public-submission-page-screen-13.md— submit API.08-security-compliance-and-privacy.md— logging and email handling.
10) Note on numbering
Section titled “10) Note on numbering”This folder already includes 05–11 numbered plans. This file is 12-… (not 05-…).