docs: plan report/block/hide, decide M8's open items, plan demo content #390
@@ -37,6 +37,7 @@ sidecar as a service, and hands you the values the website needs.
|
||||
| [SHARD_VISIBILITY.md](website/SHARD_VISIBILITY.md) | Who sees which shard data — the admin-configurable audience framework |
|
||||
| [TRUSTED_DEVICES_MFA.md](website/TRUSTED_DEVICES_MFA.md) | TOTP two-factor, trusted devices and recovery codes |
|
||||
| [STEAM_SIGNIN.md](website/STEAM_SIGNIN.md) | **Plan, built 2026-10-19** — Sign in with Steam as a built-in provider beside Google and Discord (OpenID 2.0), and a Steam id for modules |
|
||||
| [USER_SAFETY.md](website/USER_SAFETY.md) | **Plan, 2026-10-29** — report, block and hide on the website and in the app: an account-wide block, a member's own hide and staff's hide for everyone, with module-owned report targets (`MODULE_API` 1.20.0) |
|
||||
| [DEMO_MODE.md](website/DEMO_MODE.md) | **Design, fully decided 2026-10-23** — the public `demo.runicgateway.com`: one flag that locks destructive actions, static logins, and a wipe every couple of hours (website#222) |
|
||||
| [MODERATION_APPEALS.md](website/MODERATION_APPEALS.md) | Moderation actions, content reports and the appeals flow |
|
||||
| [SPAWN_ATLAS.md](website/SPAWN_ATLAS.md) | The bestiary / spawn atlas: what the shard contains, parsed from the shard's own ServUO files — served over the bridge since Protocol 8, so no shared filesystem |
|
||||
|
||||
@@ -282,7 +282,8 @@ Work items:
|
||||
2. **Deps + manifest.** Add the UnifiedPush connector + the embedded-distributor transport (per #1) to
|
||||
the version catalog; declare `POST_NOTIFICATIONS` (API 33+ runtime permission) **and
|
||||
`FOREGROUND_SERVICE` + `FOREGROUND_SERVICE_DATA_SYNC`** (API 34+, for the persistent ntfy
|
||||
connection); register the receiver and the foreground service in `AndroidManifest.xml`; define the
|
||||
connection; **now `FOREGROUND_SERVICE_SPECIAL_USE`** and `RECEIVE_BOOT_COMPLETED`, M8,
|
||||
`Android-app#62`); register the receiver and the foreground service in `AndroidManifest.xml`; define the
|
||||
notification channels (id/name externalized, §2) — one for real notifications plus a low-importance
|
||||
channel for the ongoing foreground-service notification — and reuse the "RG" notification icon
|
||||
**already staged in M6**.
|
||||
@@ -1003,20 +1004,53 @@ push, and Play (M6–M8) follow the designed app.
|
||||
for 14 days in a row** before it can apply for production. runicgateway.com `/beta`
|
||||
collects them.
|
||||
|
||||
**Open, each waiting for a decision:**
|
||||
**The three open questions, decided (org lead, 2026-10-29):**
|
||||
- **Content rating.** The questionnaire answered "Social or Communication: moderated chat;
|
||||
users or user-generated content can be reported". The app shows other players' chat (the
|
||||
Rust feed) but has no report or block action, and Play's user-generated-content policy
|
||||
expects both where that is declared. Either retake the questionnaire, or add report/block
|
||||
to the app.
|
||||
- **Data safety's premise.** The generated notes answer "not collected" almost everywhere,
|
||||
because the app talks only to a deployment the user chose. The notes call it the answer
|
||||
most likely to be questioned in review. Declaring user IDs and messages as collected,
|
||||
encrypted in transit and needed for the app to work, is the conservative alternative.
|
||||
Rust feed) but had no report or block action, and Play's user-generated-content policy
|
||||
expects both where that is declared. **Decided: keep the answer and make it true** with
|
||||
report, block and hide, on the website and in the app. The plan of record is
|
||||
[`website/USER_SAFETY.md`](../website/USER_SAFETY.md): a block lives on the account and
|
||||
applies everywhere; a member can hide an item for themself, and staff can hide it for
|
||||
everyone. In the app it goes on chat lines. The team forum in the app is planned as M21.
|
||||
- **Data safety's premise.** The generated notes answered "not collected" almost everywhere,
|
||||
because the app talks only to a deployment the user chose. **Decided: the conservative,
|
||||
more accurate answers.** Each kind of data the app sends to the deployment is declared
|
||||
collected, not shared, encrypted in transit and required for the app to work. That is
|
||||
what Play counts as collection, since the data leaves the device, whoever runs the server.
|
||||
runicgateway.com's generator (`scripts/playDataSafety.mjs`, from `src/data/collection.mjs`)
|
||||
changes to say so after an audit of what the app actually transmits. The published privacy
|
||||
page keeps saying that *we* receive nothing, which stays true.
|
||||
- **The push foreground service.** Play asks for a declaration (and a video) for the
|
||||
`dataSync` service that holds the ntfy connection. Google reserves `dataSync` for transfers,
|
||||
and Android 15 limits it to six hours a day. This brings forward §13's "FCM flavor for
|
||||
Play?" question.
|
||||
`dataSync` service that holds the ntfy connection. Google reserves `dataSync` for
|
||||
transfers, and Android 15 limits it to six hours a day. **Decided: keep UnifiedPush.** The
|
||||
service changes to the `specialUse` type with a `PROPERTY_SPECIAL_USE_FGS_SUBTYPE` reason,
|
||||
as ntfy does on Play; `specialUse` has no daily limit. **Push also restarts after a
|
||||
reboot** when the member had it on and is signed in, which `specialUse` allows from
|
||||
`BOOT_COMPLETED` and `dataSync` does not. This also fixes a crash: `PushService` does not
|
||||
handle `onTimeout`, so on Android 15+ with target 36 (`edge` since step 1) the six-hour
|
||||
cut-off would crash the app. The declaration's video is recorded on the emulator (turn push
|
||||
on, the ongoing notification, a push arriving), and the org lead uploads it. §13's "FCM
|
||||
flavor for Play?" is therefore answered **no**.
|
||||
|
||||
**The order from here** (org lead, 2026-10-29):
|
||||
1. **Push** (`edge`): `specialUse` and the restart on boot. One PR. **Built 2026-10-29** as
|
||||
`Android-app#62`. `core/push/PushBootReceiver` (not exported) listens for
|
||||
`BOOT_COMPLETED` and `MY_PACKAGE_REPLACED`, and starts `PushService` from the saved
|
||||
registration when push was on, without re-registering, because the network is often not up
|
||||
at boot. A sign-out clears the registration, so push stays off after one. Walked on the
|
||||
`s22_ultra` emulator against a local site whose relay was `ntfy.sh`: the service ran as
|
||||
`specialUse` (`types=0x40000000`), a tickle arrived, and push came back by itself after a
|
||||
reboot and after an update. After a sign-out and a reboot it stayed off.
|
||||
2. **Data safety** (runicgateway.com): the audit and the generator. One PR.
|
||||
3. **Report, block and hide** across `website`, `Module-Rust`, the kit and this app
|
||||
(`USER_SAFETY.md` §7).
|
||||
4. **The cutover**, `edge` → `main`, once 1-3 have landed, so the first Play bundle carries
|
||||
all of it. **Before it merges, the org lead enrolls the existing keystore as the app-signing
|
||||
key** in Play Console (App integrity, "use my own key"). `PLAY_SERVICE_ACCOUNT_JSON` is
|
||||
already set, so the release run will call Play, and the signing choice is permanent. On the
|
||||
draft app, CI's commit is refused as expected; the first bundle is then uploaded by hand
|
||||
(step 2 above).
|
||||
10. **M9 — Native SSO login** (post-v1, additive; independent of M8): in-app "Sign in with Google /
|
||||
Discord" via the **Mobile SSO Authorization Bridge** (§4.2). **Backend-first**, mirroring M7's
|
||||
split:
|
||||
@@ -1828,9 +1862,39 @@ push, and Play (M6–M8) follow the designed app.
|
||||
just as wrong. A maintenance error kind, read from that body, is the likely fix. It is
|
||||
outside M20 and waits for a decision.
|
||||
|
||||
22. **M21 — Teams and the team forum in the app** (post-v1; planned 2026-10-29, **not scheduled**).
|
||||
The org lead asked for it with M8's user-safety decision (`../website/USER_SAFETY.md` S4). It
|
||||
revives the deferred *Platform Teams in the app* below, whose revival trigger (Team pages,
|
||||
roster and forums on the website) has long since been met.
|
||||
|
||||
**Scope:**
|
||||
- **A Teams entry** in the menu: the member's own Teams (`GET /player/teams`) and the public
|
||||
list (`GET /public/teams`), then a Team's page (`/public/teams/:slug`, its members and
|
||||
activity).
|
||||
- **The forum**, for a Team the member can read (`GET /player/teams/:slug/access`): threads
|
||||
(`…/forum/threads`), a thread's posts (`…/forum/threads/:id`), reply and new thread
|
||||
(`POST …/threads`, `…/threads/:id/posts`), and editing their own post (`PATCH …/posts/:id`).
|
||||
Leaders get pin, lock and hide through the existing `…/moderate` routes.
|
||||
- **Report, hide for me and block** on every thread and post, as on chat lines
|
||||
(`USER_SAFETY.md` §4).
|
||||
- **Deep links** for the four Team push streams, which today open the app and no more, and the
|
||||
per-Team mute list (`GET|PUT /auth/me/notifications/teams`; its DTO field has no default,
|
||||
see the trap noted below).
|
||||
- **Uploads: view only** to begin with. Sending a file from the phone is a later decision,
|
||||
because uploads carry the operator's liability acknowledgement (`TEAMS.md` §5.5).
|
||||
|
||||
**What it changes for Play:** members can then post from the app, so the data-safety
|
||||
declaration gains "user-generated content: collected" and the content rating stays as
|
||||
answered. runicgateway.com's `collection.mjs` gains the row in the same change.
|
||||
|
||||
**Before it starts:** USER_SAFETY.md's build order is done (the report/block/hide routes M21
|
||||
reuses), and the questions it will raise (offline drafts, uploads, the order of the Teams tab
|
||||
in the menu) go to the org lead first.
|
||||
|
||||
### Deferred (not a milestone)
|
||||
|
||||
- **Platform Teams in the app** — **deferred 2026-08-17, no app work scheduled.** The website is
|
||||
- **Platform Teams in the app** — **revived as M21 on 2026-10-29** (above); the text below is the
|
||||
2026-08-17 deferral, kept for its reasoning. **Deferred 2026-08-17, no app work scheduled.** The website is
|
||||
building Teams as a core platform primitive (pages, roster, activity feed, forums, notifications —
|
||||
[`../website/TEAMS.md`](../website/TEAMS.md)), and the app has no story for any of it. Deferred
|
||||
deliberately rather than scheduled: this milestone list is already behind, and adding a large new
|
||||
@@ -2147,7 +2211,8 @@ it is a UX convenience, not a v1 requirement — deferred at M3, descoped at M6;
|
||||
No backend publish token — **decided** (the content-free-tickle design does not require one; optional
|
||||
`NTFY_PUBLISH_TOKEN` is honored if ever set).
|
||||
- FCM flavor: build it for the Play release or ship Play on UnifiedPush too? Decide at M8. (The M7
|
||||
Part 2 `PushTransport` seam keeps this swap cheap.)
|
||||
Part 2 `PushTransport` seam keeps this swap cheap.) **Decided at M8 (2026-10-29): UnifiedPush on
|
||||
Play too**, with the service moved to the `specialUse` type.
|
||||
- Deep-link / share targets for wiki pages and posts (share/open-in-app). *Notification-tap* deep-links
|
||||
are **resolved** for M7 Part 2 (stream→screen map, work item 7).
|
||||
- iOS: none planned (this is the Android-only choice); revisit only if cross-platform is later
|
||||
|
||||
@@ -498,6 +498,8 @@ down.
|
||||
| DM29 | (step-5 walk, W2, 2026-10-28) `SECRET_WORDS` also hides harmless fields such as "Allow Master Key" and "TC Auth Only". Narrow it? | **No, leave it as is** |
|
||||
| DM30 | (step 6, 2026-10-28) How does runicgateway.com `/beta` publish the logins? | **Live from the demo.** `/beta` reads this site's `GET /public/settings` `demo.logins` on its server, caches it for a minute, and links to `/account/login` when the demo does not answer (runicgateway.com D82) |
|
||||
| DM31 | (step 6) runicgateway.com's "See it running" links were UO routes, which 404 here. What do they do? | **Per game.** A deep link names its game, and that site's mounted `demoGames` (`["rust"]` for this demo) says which games' links show. Rust's point at `/rust` and `/rust/servers/demo?tab=…`, so that site now quotes the fixture's server id `demo` (runicgateway.com D83) |
|
||||
| DM32 | (2026-10-29) The moderator tour shows empty queues, and other surfaces are empty too (§11). Which get seeded? | **All four of §11's groups:** moderation history, Teams with their forum and reports, event history with the calendar, and Rust player stats |
|
||||
| DM33-DM35 | (2026-10-29) Which Steam IDs, Discord IDs and names do the invented players use? | **Visibly fake IDs** starting `1234567890`, the org lead's own Steam ID as `demo-player`'s link, and **Rust-themed invented names** (§11.3) |
|
||||
|
||||
DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is
|
||||
setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for
|
||||
@@ -647,3 +649,80 @@ through that proxy.
|
||||
locks). The org lead types the password, ticks the full-access attestation and saves. The closed
|
||||
track does not exist yet (Test and release shows *Get started* for closed and internal testing), so
|
||||
there is no opt-in URL for `betaOptInUrl`, and `/beta`'s first gate stays shut.
|
||||
7. **Demo content** (DM32, §11): the seed's two passes and the rig's clans. Planned 2026-10-29.
|
||||
One PR in `website` (core seed, the wipe's second pass), one in `Module-Rust` (`onDemoSeed`
|
||||
stats and feed lines, into `edge`), a new baseline, and a walk of every surface §11 names after
|
||||
the next wipe.
|
||||
|
||||
## 11. Demo content (DM32)
|
||||
|
||||
**Why.** A read-only count of the live demo on 2026-10-29 found these surfaces empty after a wipe:
|
||||
every moderation queue (recent actions, filter hits, spam hits, member events, appeals, reports),
|
||||
Teams (public, the player's own, staff's review queue), event runs and history, the public calendar
|
||||
(the three seeded events are never scheduled), `demo-player`'s inbox, and on the Rust side the
|
||||
leaderboard, wipes, clans and the events feed. Only the four demo accounts exist. A reviewer touring
|
||||
as `demo-moderator` finds nothing to moderate.
|
||||
|
||||
### 11.1 Two seed passes
|
||||
|
||||
Some of it can be written the moment the site is up. The rest depends on the rig: Teams are
|
||||
**module-authoritative** and re-synced every 15 minutes (`TEAMS.md`), so a Team written straight
|
||||
into core would be archived by the next sync, and the Rust stats need the wipe the rig is on. So:
|
||||
|
||||
- **Pass 1, `seed:demo`, as today**: accounts, settings, content, events, and the new core rows
|
||||
below that need nothing from the rig.
|
||||
- **Pass 2, `seed:demo --after-rig`**, a new step in `demo-wipe.yml` after the rig reports in and
|
||||
the first Teams sync has run: Team forum content, reports, Rust stats and feed lines. It waits for
|
||||
the Teams it names, with a time limit, and fails the wipe if they never appear, as the rig smoke
|
||||
check does.
|
||||
|
||||
The rig's wipe id comes from the restored world save, so it is the same on every wipe
|
||||
(`w-20261005T165054Z` today). Pass 2 reads it from the server state rather than the fixture, so a
|
||||
new baseline with a new map still works.
|
||||
|
||||
### 11.2 What each group seeds
|
||||
|
||||
- **Moderation history** (pass 1, core). Discord-shaped rows for six invented members: a few
|
||||
`mod_actions` (warn, timeout, ban), `warnings`, `filter_hits` and `spam_hits`, join and leave
|
||||
`member_events`, staff `mod_notes` (one admin-only), and three `appeals`: one pending to claim,
|
||||
one under review, one resolved. Times are relative to the seed, so the 24 h / 7 d / 30 d counts
|
||||
are never zero. There is no bot on the demo, so approving an appeal records the reversal as
|
||||
failed. That is the shipped behaviour when the bot is down, and the appeal still closes.
|
||||
- **Teams, forum and reports.** Two or three clans **in the rig's baseline** (the Clans plugin's
|
||||
data, which already reports an empty board), with stand-in members and `demo-player`'s stand-in
|
||||
Steam ID in one, so sync brings them in as Teams. One clan's name contains a reserved term, so it
|
||||
lands in staff's review queue. Pass 2 then writes forum threads and posts in them, and two open
|
||||
reports. Once report, block and hide exist (`USER_SAFETY.md`), it also writes reports on chat
|
||||
lines.
|
||||
- **Event history and the calendar** (pass 1, core). Two finished runs of the seeded events, with
|
||||
their steps, log, participants (stand-ins, and `demo-player` through its link) and published
|
||||
results, so the history and results pages fill and `demo-player`'s inbox has the results
|
||||
notification. The three seeded events are scheduled in the coming days, so the public calendar
|
||||
shows them.
|
||||
- **Rust player stats** (pass 2, Module-Rust `onDemoSeed`). About ten stand-in players with wipe
|
||||
stats (kills, deaths, NPC kills, playtime, gather totals, weapon kills) on the rig's current
|
||||
wipe, an earlier wipe for the Wipes tab, and a day of feed lines (kills, joins, chat), so the
|
||||
leaderboard, profiles and feed are populated. The online list stays honest at zero.
|
||||
|
||||
### 11.3 Identities (DM33-DM35, org lead, 2026-10-29)
|
||||
|
||||
- **DM33, Steam IDs: visibly fake, plus the org lead's own.** Every real individual Steam ID starts
|
||||
`7656119`, so an invented one in that range could belong to a stranger and would appear on public
|
||||
leaderboards. The invented players use IDs that are visibly not Steam IDs, which start
|
||||
`1234567890`: `12345678900000001`, `…02`, and so on. They are 17 digits because Module-Rust checks
|
||||
that length on links, profiles and event steps (`links.model.js`, `eventWorld.js`), and a shorter
|
||||
one would break those pages. `demoSeed.js` currently insists on `7656119…` for links and is
|
||||
widened for these. The one real Steam ID on the demo is the org lead's own (`76561198038695917`),
|
||||
and it **replaces `demo-player`'s stand-in link** (`76561199910000001`, which could belong to
|
||||
anyone): confirmed by the org lead. So whoever signs in as `demo-player` sees that ID linked, and
|
||||
the org lead's own play on the demo rig shows under `demo-player`. The default set's `vip`
|
||||
membership (§10 step 5, DM26), which names the old stand-in on the rig, moves to this ID in the
|
||||
new baseline that §11's clans need anyway.
|
||||
- **DM34, Discord IDs: the same rule.** The invented moderation history uses visibly fake ids
|
||||
starting `1234567890`, padded to a snowflake's 18 digits. Confirmed by the org lead.
|
||||
- **DM35, names: Rust-themed and invented.** Fun gamer tags from the game's world, such as
|
||||
"Rusty_Pete", "Scrap Baron", "Naked_On_The_Beach", "Rock Enthusiast", "Big Oil Bandit", "TC
|
||||
Thief", "Hazmat Hank" and "Bean Can Dan", and clans such as "The Scrap Collective" and
|
||||
"Outpost Loiterers". Never a real person's name, and nothing offensive: the seeded filter hits
|
||||
show a placeholder word.
|
||||
|
||||
|
||||
130
website/USER_SAFETY.md
Normal file
130
website/USER_SAFETY.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user