Moderation
When a member reports content in your church, the report lands in the admin Moderation queue. From there a moderator (any church admin) can Hide the content pending review, Remove it outright, or Dismiss the report without touching the content.
Moderation is always available — there's no module_moderation
flag. Even if every community module is off the queue still receives
reports for content surfaces the church has used.
How does a member report something?
Every member-facing piece of generated content carries a small
Report affordance (a flag icon menu or button) next to it. Tapping
it opens a short form asking what's wrong — 1 to 1000 characters of
free text. The submission goes into content_reports with status
open; the reporter is stamped as reporter_id.
Reports are admin-only — the reporter doesn't see them again in the UI, and the reported person never knows who reported them.
Where the Report button lives today
| Content type | Web | Mobile |
|---|---|---|
| Directory profiles | ✅ | ✅ |
| Prayer requests | ✅ | ✅ |
| Group posts | ✅ | ✅ |
| Group comments | ✅ | ✗ (fast-follow) |
| Announcement reactions | ✗ (no plan — see below) | ✗ |
The missing pieces are tracked in
phases/phase-4-deferrals.md.
How do I open the queue?
In the admin sidebar, open Community → Open moderation. The queue lists reports in five tabs:
- Open — newly submitted, never reviewed. The default view.
- Reviewed — an admin marked it as seen but didn't act on it.
- Actioned — the moderator hid or removed the content. The report is closed but the action note is recorded.
- Dismissed — closed without touching the content.
- All — everything, regardless of status.
Each row shows:
- The content type as a badge (Prayer request, Group post, …).
- The report status as a second badge, plus Hidden / Deleted flags if the content has already been moderated.
- The reporter's reason — the message they entered.
- The content excerpt in a quoted block, with the author's name.
- A "View in context" link that drops you onto the feature surface where the content lives.
What do the actions do?
Hide
Sets hidden_at = now() on the target row. The author and other
admins still see the content (so the author can edit it before it
comes back); every other member sees nothing. RLS does this — there's
no client-side filter to forget.
Supported on:
- Prayer requests
- Prayer encouragements
- Group posts
- Group comments
Hide also flips the report to Actioned with a "Hidden pending
review" note. Un-hide restores visibility (clears hidden_at).
Remove
Removes the content from member views permanently. Where the table
supports soft-delete (deleted_at), it's reversible by a database
operator; for chat messages without deleted_at the row is hard-deleted.
Supported on:
- Prayer requests (soft)
- Group posts (soft)
- Group comments (soft)
- Group messages (hard)
Encouragements aren't Remove-able by an admin (Supabase RLS lets only the author delete them) — use Hide instead.
Remove also flips the report to Actioned with a "Removed" note.
Dismiss
Closes the report without touching the content. Use this when the reported content is fine. The reporter isn't notified; if they report the same content again, a new row lands in the queue.
Mark reviewed
A middle state. The admin has looked at the report but doesn't want to act yet — perhaps they're going to talk to the author offline. The report moves to the Reviewed tab and stops cluttering the Open queue.
Re-open
Resolved reports (Actioned / Dismissed) carry a Re-open button. Bring a report back to the Reviewed state if circumstances change.
Audit trail
Every Hide / Remove / Dismiss / Mark-reviewed action stamps:
resolved_by—auth.uid()of the moderator.resolved_at— when the action happened.resolution_note— a short string explaining what was done.
These columns are admin-only in the UI today. The full per-row history
lives in the existing audit_log (the content tables carry the audit
triggers from Phase 0).
Privacy
- Members do not see other members' reports.
- The reported person doesn't know they were reported. (When their content is hidden, the visible state changes; they may infer it.)
- The reporter's identity is admin-only.
resolution_noteis admin-only — it's a moderator-internal note, never surfaced to either the reporter or the content's author.
Future / fast-follows
- Member-facing report status ("your report was actioned") — out of scope per the brief.
- Automated moderation / keyword filtering — out of scope; manual for v1.
- Moderation of admin-authored content (announcements) — out; the intended model is "admins moderate themselves" via the announcements editor.
- Mobile parity for the Report button across group posts + group comments — fast-follow (deferrals doc). (Prayer requests + directory profiles have mobile Report as of the tier-7 community remediation.)
- Web Report button on prayer encouragements + group messages + announcement reactions — fast-follow.
- Announcement-reaction moderation has a schema gap (the table's
PK is composite, so a single
content_iduuid can't address a reaction). Reports of that kind surface withcontent: nullin the queue and can be dismissed. A real fix would either add anidPK toannouncement_reactionsor move the reportable content to a proper reactions-with-id table.
More topics