- USER_SAFETY.md: section 5 becomes decisions S6-S10 (an account to report, game-identity blocks allowed, staff cannot be blocked, a block only hides, staff see hidden chat marked). - DEMO_MODE.md: DM33-DM35 (visibly fake 1234567890-prefixed Steam and Discord ids plus the org lead's own Steam id; Rust-themed invented names). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
131 lines
9.3 KiB
Markdown
131 lines
9.3 KiB
Markdown
# Report, block and hide — user safety on the website and in the app
|
|
|
|
**Status:** plan of record, 2026-10-29. Nothing built yet. Each phase stops for the org lead
|
|
before the next one starts.
|
|
|
|
**Why now.** Google Play's content-rating questionnaire for the Android app (`android/PLAN.md` M8)
|
|
was answered "users or user-generated content can be reported". The app shows other players' chat
|
|
(the Rust live feed) and has no way to report it or to stop seeing someone. Play's user-generated
|
|
content policy expects both wherever that answer is given. The org lead chose to make the answer
|
|
true rather than retake the questionnaire (S1).
|
|
|
|
## 1. What a member can do
|
|
|
|
| Action | Who | Where | Effect |
|
|
|---|---|---|---|
|
|
| **Report** | any signed-in member | a chat line (web + app), a forum thread, post or upload (web; app later, §6) | a row in the existing **Reports** queue (`TEAMS.md` §5.6). Changes nothing on its own |
|
|
| **Hide for me** | any signed-in member | the same items | that one item disappears from **their own** view, on every device. No report is filed |
|
|
| **Block** | any signed-in member | the author of any of those items, unless the author is staff (S8) | everything that author writes (chat lines and forum content) disappears from the blocker's view, on the website and in the app, on every device. Undone from *Account → Blocked* |
|
|
| **Hide for everyone** | staff (and, for their own forum, Team leaders, as today) | the Reports queue, the forum's existing moderation menu, and the item's own menu | the item disappears for everybody except staff, who see it with a *Hidden by staff* marker and can undo it |
|
|
|
|
A report and a hide are separate on purpose, as `TEAMS.md` §5.6 already says: filing a report must
|
|
never be a way to hide something. *Hide for me* is the member's own tool, and *Hide for everyone* is
|
|
staff's.
|
|
|
|
## 2. Decisions (org lead, 2026-10-29)
|
|
|
|
| # | Question | Answer |
|
|
|---|---|---|
|
|
| S1 | The questionnaire says chat is reportable, and it is not. Retake it, drop chat from the app, or add report/block? | **Add report, block and hide.** The questionnaire stays as answered |
|
|
| S2 | Where does a block live? | **On the account, everywhere.** Saved on the site against the blocker's account, so it applies on the website and in the app, on every device |
|
|
| S3 | "Hide": who hides, and from whom? | **Both.** A member can hide an item for themself, and staff can hide it for everyone |
|
|
| S4 | The app has no Teams or forum screens. Does the app get the forum? | **Plan it** (§6, `android/PLAN.md` M21). In the app, report/block/hide go on chat lines now |
|
|
| S5 | When does the Android `edge` → `main` cutover happen? | **After this feature ships**, so the first Play bundle already has it |
|
|
|
|
## 3. What exists today
|
|
|
|
- **Reports.** `content_reports` (`TEAMS.md` §5.6) is generic by design, but
|
|
`contentReports.model.js` accepts only `team_forum_thread`, `team_forum_post` and
|
|
`team_forum_upload`, and finds the owning Team for each. Filing is
|
|
`POST /player/teams/:slug/forum/report`. The queue is `GET /admin/moderation/reports`, and
|
|
`POST /admin/moderation/reports/:id/handle` claims, actions or dismisses a report. "Actioned" only
|
|
records a decision; it does nothing to the content.
|
|
- **Staff hide in the forum.** `team_forum_moderation` already records `hide`/`unhide` (and pin,
|
|
lock, delete) by a leader or staff, through `POST …/forum/threads/:id/moderate` and
|
|
`…/posts/:id/moderate`.
|
|
- **Chat lines.** Each one is a `rust_events` row in Module-Rust (`kind = 'player.chat'`, with an
|
|
`id`, `server_id`, `steam_id`, time and raw frame). The website and the app both read them from
|
|
the public `GET /public/rust/servers/:id/events`. The app polls it signed out or in.
|
|
- **No blocks or per-member hides** exist anywhere.
|
|
|
|
## 4. Design (§5 holds the decisions that shape it)
|
|
|
|
### 4.1 Core (`website`)
|
|
|
|
- **Module-owned report targets.** A new optional module hook, `reportTargets`, declares the types
|
|
a module's content can be reported as (for Rust: `rust.chat`). Each type answers two questions for
|
|
core: `snapshot(id)` (author, text, time, where it was said), and `hide(id, actor)` /
|
|
`unhide(id, actor)`. Core files the report, stores the snapshot and runs the queue. The module owns
|
|
the content and its hidden flag. This is a `MODULE_API` minor bump, **1.20.0**. The Integration
|
|
Kit re-pins and gains a paragraph.
|
|
- **One filing route for every target**: `POST /player/reports {targetType, targetId, reason,
|
|
detail?}`. Forum types keep working through the existing forum route, which then calls the same
|
|
model.
|
|
- **`content_reports.snapshot JSON`**: what the item said when it was reported. Chat lines are
|
|
pruned with the rest of `rust_events`, and a queue row must still show what was said after that.
|
|
Forum reports fill it too, so an author editing a post after the report cannot change what staff
|
|
read.
|
|
- **The queue gains *Hide for everyone*** beside actioned and dismissed. It calls the forum's
|
|
existing hide or the module's `hide`, and records the report as actioned with that action named.
|
|
- **Blocks**: `user_blocks (blocker_user_id, kind 'user'|'game', blocked_user_id NULL, module_id
|
|
NULL, external_id NULL, display_name snapshot, created_at)`. A game block names a game identity
|
|
(for Rust, a Steam ID), because most people in chat have no site account. When that identity is
|
|
linked to an account, blocking either one hides both.
|
|
- **Per-member hides**: `user_hidden_items (user_id, target_type, target_id, created_at)`.
|
|
- **Routes**: `GET/POST/DELETE /auth/me/blocks`, `GET/POST/DELETE /auth/me/hidden`. Their
|
|
OpenAPI annotations and spec are regenerated as usual.
|
|
- **Filtering.** Signed-in forum reads drop blocked authors and hidden items on the server. The
|
|
public chat feed is shared and cached, so it cannot be filtered per reader on the server. The
|
|
website and the app filter it with the member's lists instead, read once per session and kept
|
|
current after each block or hide. Module client chunks reach those lists through a new client
|
|
module-API call.
|
|
- **Web UI.** The forum's thread and post menus gain *Hide for me* and *Block author*, beside the
|
|
existing *Report*. A new *Account → Blocked* page lists blocks and undoes them.
|
|
|
|
### 4.2 Module-Rust
|
|
|
|
- `reportTargets['rust.chat']` with `snapshot` (the speaker's name and Steam ID, the line, server,
|
|
time, and the linked account if there is one) and `hide`/`unhide`.
|
|
- **Hide for everyone** sets a flag on the `rust_events` row (`hidden_at`, `hidden_by`). The events
|
|
endpoint leaves hidden chat lines out for everybody except staff, who get them marked.
|
|
- **The live feed's chat lines gain a menu**: *Report*, *Hide for me*, *Block player*, and for
|
|
staff *Hide for everyone*.
|
|
- On the demo, all of this stays **open**: it is what a Play reviewer tests, and the wipe resets it.
|
|
|
|
### 4.3 Android app (chat lines only, S4)
|
|
|
|
- **Long-press a chat line** for the same menu. Report opens a sheet with the six reasons and an
|
|
optional detail. A member who is signed out gets *Sign in to report or block*.
|
|
- **Account → Blocked** lists blocks and undoes them, mirroring the web page.
|
|
- The feed filters with the member's lists, as in §4.1.
|
|
|
|
## 5. Further decisions (org lead, 2026-10-29)
|
|
|
|
| # | Question | Answer |
|
|
|---|---|---|
|
|
| S6 | Can a signed-out visitor report a chat line? | **No.** Reporting, hiding and blocking all need an account, as forum reports do today, so the queue is not open to anyone with `curl`. A signed-out viewer sees *Sign in to report or block* |
|
|
| S7 | Can a member block a game identity (a Steam ID) that is linked to no account? | **Yes** (§4.1's game block). Almost everyone in a Rust chat feed is unlinked |
|
|
| S8 | Can a member block staff? | **No.** The block is refused when its target is a staff account (admin, editor or moderator), or a game identity linked to one, and the menu does not offer it on staff content. A game block whose identity later links to a staff account stops applying, and *Account → Blocked* says why. *Hide for me* still works on a single staff item |
|
|
| S9 | What does a block do to the blocked person? | **Nothing beyond hiding** their content from the blocker. They are not told; there are no direct messages to stop |
|
|
| S10 | Do staff still see a chat line hidden for everyone? | **Yes, marked** *Hidden by staff*, so the hide can be checked and undone, as the forum already treats its own hidden posts |
|
|
|
|
## 6. The team forum in the app (planned, not scheduled)
|
|
|
|
The app has no Teams screens. `android/PLAN.md` M21 plans them: the member's Teams, a Team's page,
|
|
the forum's threads and posts (read, reply, new thread), the same report/hide/block menu on posts
|
|
and threads, and the forum's push stream. When it ships, the app lets members post content, so the
|
|
Play declarations gain "user-generated content: collected". M21 says so.
|
|
|
|
## 7. Build order
|
|
|
|
1. **This plan** (docs), with §5's answers.
|
|
2. **Core** (`website`): §4.1, `MODULE_API` 1.20.0, tests, OpenAPI, `BACKEND_DESIGN.md`,
|
|
`MODULE_API.md`, `TEAMS.md` §5.6 amended. One PR.
|
|
3. **Module-Rust** (`edge`): §4.2 plus the core-ref pin. One PR. **Integration-kit**: the re-pin and
|
|
a paragraph on `reportTargets`. One PR.
|
|
4. **Android** (`edge`): §4.3. One PR.
|
|
5. **A walk** on the demo after the next wipe: report, hide and block, each on the web and in the
|
|
app; *Hide for everyone* from the queue; a blocked Steam ID that later links to an account stays
|
|
blocked.
|
|
6. Then `android/PLAN.md` M8's cutover (S5).
|