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 typeWebMobile
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 CommunityOpen 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:

  1. The content type as a badge (Prayer request, Group post, …).
  2. The report status as a second badge, plus Hidden / Deleted flags if the content has already been moderated.
  3. The reporter's reason — the message they entered.
  4. The content excerpt in a quoted block, with the author's name.
  5. 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_byauth.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_note is 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_id uuid can't address a reaction). Reports of that kind surface with content: null in the queue and can be dismissed. A real fix would either add an id PK to announcement_reactions or move the reportable content to a proper reactions-with-id table.