Skip to content

Messages near the size limit are silently lost: UI gate counts raw text bytes but the contract enforces encoded size #430

Description

@sanity

Problem

Reported by HostFat on Matrix (2026-07): a message under 1000 characters was rejected after send with the console showing

WARN ui/src/components/conversation.rs:1720 Message too long: 1006 encoded bytes, max 1000 bytes

and the message was silently lost — no error shown, draft cleared. Earlier the same session: "a message was lost" and "a very long message was't sent :(".

Root cause

Three checks measure different things:

  1. Input gate + byte counter (message_input.rs): raw text.len()max_message_size.
  2. Client safety net (conversation.rs handle_send_message): RoomMessageBody::content_len() — the encoded content. CBOR framing adds ~9 bytes for plain text; replies embed the quoted author name + ~100-char preview (~150 bytes); private rooms add the 16-byte AES-GCM tag.
  3. Contract validation (message.rs): prunes messages with content_len() > max_message_size — the authoritative limit is encoded bytes.

Any message in the gap between (1) and (2) passes the UI, has its draft cleared by send_message, then is dropped by the safety net with only a console warning. For replies the gap is ~150 bytes, so even ~850-char replies vanish. Multi-byte UTF-8 widens the perceived gap further (users count characters; the limit counts bytes).

Edits have no size gate at all: an over-limit edit action (ActionContentV1 overhead on top of the text) is signed, sent, and silently pruned by contract validation on every peer.

riverctl is not affected — it builds the real body and errors visibly (guard_message_size).

Fix

Measure the exact encoded size in the UI using the same construction code path the senders use (RoomMessageBody::measure_text/measure_reply/measure_edit in river-core, pinned equal to real built bodies by tests), gate Send/Save and the byte counter on that, and keep the draft in place when over the limit.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions