Built 2026-10-03. Any member may rename a room, and the name changes for everybody in it. There is no private nickname beside it. Before this, a room's name came only from its link and was kept per device: Android's "Rename" on the saved rooms list changed that device's copy alone, and the web and desktop clients had no rename at all. Now the web and desktop clients rename from the room's details ("Rename for everyone"), and Android's rename is to become the same.
The reference implementation is src/room-name.ts (the op, the order, the
book of renames, followRoomName) and src/control.ts (the codec). The
interop vectors are the roomName group in vectors/kithmoot-vectors.json.
A rename is a control message: a name op in the text of an ordinary chat
message on the room's control channel.
{"op":"name","name":"Book club","id":"0123456789abcdef0123456789abcdef","at":1800000100250}That puts it inside an envelope that already exists and already says everything a rename has to:
- Who. The chat message is signed by the sender's device and carries the
device credential binding it to the participant, checked by
decodeChatEventwith the room's admission policy. A rename from outside the room, or in somebody else's name, does not decode. - Readable by members only, and only for their epochs. The control
channel derives from the current epoch's id and key
(
deriveChannel(epochId, epochKey, "control")), so itsdand its key move at every rekey like every other channel. - Nothing new on the wire. A relay sees a kind-1460 event, a
dtag it cannot link to the room, a device key, a second-resolutioncreated_atand a NIP-44 ciphertext, which is exactly what it sees of the control traffic every room already has (catalogue?,nudge,relays). The name is inside the plaintext, padded by NIP-44 as all chat text is, so its length is hidden to the padding bucket and no further.
The main chat was the other candidate, as a statement field like a reaction.
It was turned down for two reasons. The main chat holds at most 500 messages
and 30 days, so in a busy room a rename falls out of what a newcomer reads
within the day; the control channel is quiet. And carried copies (below)
would show as messages on every client that has not heard of renames; on
the control channel an older client drops an op it does not know, as
decodeControl returns null for one.
docs/decisions.md says nothing a chat message says may change membership,
roles or structure, which is why the channel list and the admin list are
signed by the authority. A name is none of those: it is a label, like a
display name, and any member may set it. That is a product decision, made
deliberately, and the reason a rename needs no signature beyond the
sender's own.
name: a string of at most 32 code points (MAX_DISPLAY_NAME_LENGTH), sanitised as a display name and a link'snare (sanitiseDisplayName). Longer is refused rather than cut; one that sanitises to nothing is refused. A writer sanitises and cuts before sending, where the person can see it.id: 32 lower-case hex characters, chosen at random by whoever renamed.at: unix milliseconds, when the rename was made. A safe integer above 0.carried: absent, ortrueon a copy posted again by any member. Any other value is refused.
A reader also refuses a rename whose floor(at / 1000) is later than the
sentAt of the message carrying it, so a member cannot date a rename into
the future and pin it above every later one: the most at can do is what
renaming now would do.
The renamer is the carrying message's participant. A carried copy names
nobody, because the member who posted it again only repeated it, and a
reader shows " renamed the room to …" only for a rename that is not
carried, once per id.
Newest wins, in the message order of docs/messages.md: by at, then by
id, then by the name itself, each compared as plain values (compareRoomNames).
Every device holding the same renames shows the same name, whatever order
they arrived in.
The winning name replaces this device's copy of the room's name everywhere:
the title, the saved room, the rooms list, and every link the device hands
on, whose n is the current name. A device keeps the winning rename with
its order key (kithmoot.room-name.v1.<roomId> in the web client), so a
link written before a rename does not put the old name back.
The web client's rooms list reads renames in rooms that are not open, off
the same control channel, with the same read-only log it reads their chat
with (RoomWatch). It reads under the epoch the room was in when this
device was last inside it: the client keeps that epoch's id and key beside
the room secret it already holds (kithmoot.room-epoch.v1.<roomId>), and
forgets them with the room. A watch holds no device key, so it cannot open
a rekey by itself. If the room rekeyed while it was closed here, the list
keeps the name it had until the room is opened, which catches it up. A
rename the list reads replaces the kept one only when it orders later, so a
list reading an epoch the room has left cannot pull a name back.
A rename is readable only by members holding the epoch it was posted under. A member removed at a rekey cannot read the next epoch's control channel, so no name set after their removal reaches them.
The name survives a rekey because members carry it: a device in a new
epoch whose control log holds no copy of the current name posts the same
rename again, with carried: true and the original's name, id and at
unchanged. The copy orders exactly where the original does, so it can never
outrank a later rename made meanwhile, and a duplicate from two members is
harmless. A member admitted at a later epoch, who never held the earlier
keys, reads the name from that copy. The web client tries a few seconds
after each rekey, with a random delay so members do not all post at once.
The same rule keeps the name inside the relays' window: a device that finds the newest copy in the current epoch older than 20 days, or none at all while it holds a kept rename, posts it again a while after opening the room. That is the room relays record's rule, at the same interval.
A device that missed a rekey and reads under the epoch the room left may
see renames that a removed member posted there after the rekey. Once it
catches up, a rename read under a left epoch counts only if its at is no
later than the rekey out of that epoch (its signed created_at) plus 300
seconds, the codec's clock-skew bound (RoomNameBook.current). What is left
is honest to say: a removed member can backdate a rename to before their
removal and so show it to devices that had not yet heard of the rekey, and
a later rename by anybody in the room beats it. It is the most they could
have done while they were a member.
roomName in vectors/kithmoot-vectors.json, each vector carrying what it
needs to decode: a rename under epoch 0 and under epoch 1, the epoch-1 event
read with epoch 0's keys (no decode), a carried copy, a hostile name
sanitised, and the refusals (empty, over length, stamped after its message,
an upper-case id, carried: false, a device signing in another's name).
Three order vectors pin the tie-breaks, and left-epoch-cut pins the rule
for renames read under a left epoch. Timing - when a device carries the
name - is not pinned, as no timing is.