# The messenger

2026-08-08. Asked for directly:

> "the Messages component looks basic as can be. I want it to pop as well."

> "Make sure redesigning Messages to look not so basic. Add the ability to add
> people to messages too."

with a reference image of a three-pane chat client, and one constraint that
settles the biggest question before it is asked:

> "Something like this would look amazing but using the built in editor in
> Convoro."

---

## What is actually wrong with it

It is not ugly. It is a **forum thread wearing a different noun**.

A conversation renders as a page header, a participants strip, a vertical list
of `<div class="message">` each with a name, a timestamp and a body, and a
"Reply" heading over a form. That is the topic page with the word Message in
it — and it reads wrong, because private conversation is not threaded reading.
Three specific things it gets wrong:

**Every message weighs the same.** Yours and theirs render identically apart
from a class nothing much uses. In a two-person conversation the single most
useful piece of information on the screen is *who said which line*, and it is
carried only by a name in small text above each one.

**Every message repeats the same header.** Six messages in a row from the same
person means their name and the date six times, which is six lines of chrome
around six lines of content.

**Reading and choosing are different pages.** Going from one conversation to
another means going back to a list and forward again. The list is where you
choose; the conversation is where you read; making them the same screen is the
single change that makes this feel like a messenger rather than a forum.

## The shape

Three panes, and each earns its place:

- **The conversations**, down the left. Avatar, who it is with, the opening of
  the last message, when, and an unread mark. This is a *chooser*, so it shows
  what somebody chooses by — not a subject line and a message count.
- **The conversation**, in the middle. Bubbles: yours on the right, theirs on
  the left with their avatar. A date separator when the day changes. Runs from
  the same person collapse into one group with one name and one time.
- **Who is in it**, on the right. Every participant, the control that adds
  somebody, and the one that leaves. On a narrow screen it collapses; a phone
  gets the list *or* the conversation, never a third of each.

## 🚨 The composer is Convoro's editor, not a chat input

Stated by the person asking, and it is the right call for a reason worth
writing down: **a private message is a post.** It is written by a member, it
holds the same rich content, it renders through the same TipTap renderer, and
it already stores the same three columns — `content`, `content_html`,
`content_plain`. A bare `<input>` styled like a chat app would be a second,
worse writing surface with no mentions, no formatting, no attachments and no
drafts, and every one of those would then get asked for separately.

So the composer is the same editor as a reply, docked to the bottom of the
conversation and sized for one or two lines rather than for an essay.

## Adding people

The table has always supported it — `conversation_participants` is a join with
a `has_left` flag — but nothing ever inserted a second row after the first
message. So:

**Anybody in the conversation may add somebody.** Not only the person who
started it. The alternative is a conversation whose creator has left and which
can never include anybody again, and "ask the person who started this to add
them" is not a thing anybody should have to say.

🚨 **A person added sees the whole history, and the screen says so before you
add them.** This is the one genuinely dangerous thing in the feature: private
messages are private, and quietly showing three months of conversation to a
newcomer because somebody clicked Add is exactly the kind of accident that
cannot be taken back. Hiding what came before would be worse — a conversation
half of whose participants can see a message is a conversation nobody can
reason about.

🚨 **Somebody who left and is added again is restored, not duplicated.** The
unique key on (conversation, user) means the naive insert throws; the naive
`INSERT IGNORE` means they are silently not added. Neither is right: clearing
`has_left` is.

**Nobody can be removed by somebody else.** Only leaving. Being thrown out of a
private conversation by another member is a moderation power, and this is not
moderation — the message is already sent and they have already read it.

## What this note does not decide

- **Typing indicators and read receipts.** Both are wanted in a design like
  this, both are realtime work rather than layout work, and read receipts in
  particular are a privacy decision more than a feature.
- **Attachments in a message.** The editor supports them for posts; wiring the
  same upload path here needs a story about who may fetch a file attached to a
  private conversation, and that story does not exist yet.
- **Searching your messages.** Wanted the moment somebody has a hundred of
  them. The content-type registry is the obvious seam, and 🚨 it is the seam
  where a leak would be worst — private messages must never turn up in anybody
  else's search results, including an administrator's.
