Summary
Member timeout (a.k.a. moderation timeout / communication_disabled_until) is not functional end to end. The Flutter client and the MCP both send the request correctly, but the server silently ignores it: the field is never deserialized, never written to the DB, and never enforced. The PATCH returns 200 OK with the member unchanged, so the client believes the timeout succeeded (a false success — worse than an error).
The DB already has a timed_out_until column; it is read back in SELECTs and serialized in member responses, but nothing ever sets it.
How the client uses it
The client (and MCP timeout_member) call:
PATCH /spaces/{space_id}/members/{user_id}
{ "communication_disabled_until": "2026-06-14T12:00:00Z" } // or null to clear
It gates the UI on the moderate_members permission and renders a "timed out" badge from timed_out_until in the member object.
Server gaps
- Field is dropped on deserialize.
UpdateMember (src/models/member.rs:34-40) only declares nickname, avatar, roles, mute, deaf. There is no communication_disabled_until / timed_out_until, so serde discards the unknown field and update_member proceeds as a no-op for timeouts (src/routes/members.rs:102).
- DB never writes it.
db::members::update_member (src/db/members.rs:121) only emits UPDATEs for nickname/avatar/roles/mute/deaf. No code path assigns timed_out_until.
- No permission gate for timeout.
update_member checks manage_nicknames / manage_roles / mute_members / deafen_members, but never moderate_members for a timeout. (The permission exists and is used by src/routes/reports.rs.)
- No enforcement.
src/routes/messages.rs has zero timeout checks — a timed-out user can still send messages (and should also be blocked from reactions, typing, voice connect, etc.).
Proposed fix
- Add
communication_disabled_until: Option<Option<String>> (or equivalent nullable) to UpdateMember, accepting both communication_disabled_until and timed_out_until as input keys; an explicit null clears the timeout.
- Gate setting/clearing it behind the
moderate_members permission (plus the existing require_hierarchy check so you can't time out someone above you).
- Persist it in
db::members::update_member (UPDATE members SET timed_out_until = ? ...), or add a dedicated route if preferred.
- Enforce it: reject message create (and ideally reactions, typing, voice connect) with a 403 while
timed_out_until is in the future. Treat past/expired timestamps as not-timed-out.
- The
member.update gateway broadcast already fires after update_member (src/routes/members.rs:170-185), so clients will reflect the change once it's persisted — no gateway work needed.
Acceptance criteria
Related
- Client UI:
daccord lib/features/member/views/accord_member_popout.dart
- MCP tool:
daccord lib/features/developer/services/mcp_tools.dart (timeout_member)
Summary
Member timeout (a.k.a. moderation timeout /
communication_disabled_until) is not functional end to end. The Flutter client and the MCP both send the request correctly, but the server silently ignores it: the field is never deserialized, never written to the DB, and never enforced. ThePATCHreturns 200 OK with the member unchanged, so the client believes the timeout succeeded (a false success — worse than an error).The DB already has a
timed_out_untilcolumn; it is read back in SELECTs and serialized in member responses, but nothing ever sets it.How the client uses it
The client (and MCP
timeout_member) call:It gates the UI on the
moderate_memberspermission and renders a "timed out" badge fromtimed_out_untilin the member object.Server gaps
UpdateMember(src/models/member.rs:34-40) only declaresnickname, avatar, roles, mute, deaf. There is nocommunication_disabled_until/timed_out_until, so serde discards the unknown field andupdate_memberproceeds as a no-op for timeouts (src/routes/members.rs:102).db::members::update_member(src/db/members.rs:121) only emitsUPDATEs for nickname/avatar/roles/mute/deaf. No code path assignstimed_out_until.update_memberchecksmanage_nicknames/manage_roles/mute_members/deafen_members, but nevermoderate_membersfor a timeout. (The permission exists and is used bysrc/routes/reports.rs.)src/routes/messages.rshas zero timeout checks — a timed-out user can still send messages (and should also be blocked from reactions, typing, voice connect, etc.).Proposed fix
communication_disabled_until: Option<Option<String>>(or equivalent nullable) toUpdateMember, accepting bothcommunication_disabled_untilandtimed_out_untilas input keys; an explicitnullclears the timeout.moderate_memberspermission (plus the existingrequire_hierarchycheck so you can't time out someone above you).db::members::update_member(UPDATE members SET timed_out_until = ? ...), or add a dedicated route if preferred.timed_out_untilis in the future. Treat past/expired timestamps as not-timed-out.member.updategateway broadcast already fires afterupdate_member(src/routes/members.rs:170-185), so clients will reflect the change once it's persisted — no gateway work needed.Acceptance criteria
PATCH /spaces/{id}/members/{user}withcommunication_disabled_untilpersiststimed_out_untiland returns the updated value.moderate_members(+ hierarchy); unauthorized callers get 403.nullclears the timeout.Related
daccordlib/features/member/views/accord_member_popout.dartdaccordlib/features/developer/services/mcp_tools.dart(timeout_member)