feat(room): per-room nickname (LC-321) #337

Merged
longjacksonle merged 1 commit from feat/lc-321-per-room-nickname into main 2026-06-17 01:37:07 +02:00

What

Per-room nickname: a user can set a display name that applies only within one room, overriding their global display name (and username fallback) wherever their messages render in that room. Implements LC-321 / LC-322. Their username is untouched, so @mentions, DM deep-links, and avatar initials keep working.

How

  • Single override point. Every message author label flows through routes::load_author_meta. It (and its wrapper resolve_msg_author) now take a room_id; for the real-user branch only, display_name is replaced with the per-room nickname when one is set. Synthetic actors (webhook/email/bridge) return before this, so they never pick up a nickname. No MessageView shape change: the room page, DM page, the three live WS renders, and thread/edit renders all get the nickname for free. The page builder's per-author cache is per-room-render, so caching the nickname-folded meta is correct.
  • Storage. New chat.db::room_nicknames (migration 0062, PK room_id+user_id, room_id cascades; user_id references auth.db so no cross-db FK, matching room_members). db::room_nicknames exposes set (upsert, 32-char cap -> typed error), clear, get, for_room.
  • UI. A "Your nickname in this room" form on the room info docs tab, pre-filled. POST /room/{id}/nickname is self-only and room-access-gated; empty/absent field clears. The shared room/nickname_section.html partial is rendered by both the page and the POST response, swapping #room-nickname in place.
  • i18n. New room.ftl keys, en/es parity.

Scope / assumptions

  • Self-set only (you set your own nickname); moderator-assigned nicknames are out of scope for v1.
  • Per-room (per the request), not per-enclave; the same user in a different room without a nickname shows their global name.
  • Live-update bound. New and edited messages render with the nickname immediately; messages already painted in an open tab keep the old label until the next render or reload. A full-backlog re-render on nickname change is deliberately not attempted (it would be a clobbering full-feed swap, against the LC-179 precedent).
  • The override applies to the message author label only. The enclave member list (enclave-scoped) and email notification labels keep the global name.

Testing

  • just check (clippy standalone + saas, fmt) clean.
  • just test and just test-saas green. New db_room_nicknames.rs covers set/get/clear roundtrips, upsert-replace, clear no-op, per-room scoping, the 32-char cap (reject + exact-max), and for_room.

Operator impact

None. No new env var, config, or contract change; no [operator-action] marker.

🤖 Generated with Claude Code

## What Per-room nickname: a user can set a display name that applies only within one room, overriding their global display name (and username fallback) wherever their messages render in that room. Implements LC-321 / LC-322. Their `username` is untouched, so @mentions, DM deep-links, and avatar initials keep working. ## How - **Single override point.** Every message author label flows through `routes::load_author_meta`. It (and its wrapper `resolve_msg_author`) now take a `room_id`; for the real-user branch only, `display_name` is replaced with the per-room nickname when one is set. Synthetic actors (webhook/email/bridge) return before this, so they never pick up a nickname. No `MessageView` shape change: the room page, DM page, the three live WS renders, and thread/edit renders all get the nickname for free. The page builder's per-author cache is per-room-render, so caching the nickname-folded meta is correct. - **Storage.** New `chat.db::room_nicknames` (migration `0062`, PK `room_id`+`user_id`, `room_id` cascades; `user_id` references auth.db so no cross-db FK, matching `room_members`). `db::room_nicknames` exposes `set` (upsert, 32-char cap -> typed error), `clear`, `get`, `for_room`. - **UI.** A "Your nickname in this room" form on the room info docs tab, pre-filled. `POST /room/{id}/nickname` is self-only and room-access-gated; empty/absent field clears. The shared `room/nickname_section.html` partial is rendered by both the page and the POST response, swapping `#room-nickname` in place. - **i18n.** New `room.ftl` keys, en/es parity. ## Scope / assumptions - **Self-set only** (you set your own nickname); moderator-assigned nicknames are out of scope for v1. - **Per-room** (per the request), not per-enclave; the same user in a different room without a nickname shows their global name. - **Live-update bound.** New and edited messages render with the nickname immediately; messages already painted in an open tab keep the old label until the next render or reload. A full-backlog re-render on nickname change is deliberately not attempted (it would be a clobbering full-feed swap, against the LC-179 precedent). - The override applies to the message author label only. The enclave member list (enclave-scoped) and email notification labels keep the global name. ## Testing - `just check` (clippy standalone + saas, fmt) clean. - `just test` and `just test-saas` green. New `db_room_nicknames.rs` covers set/get/clear roundtrips, upsert-replace, clear no-op, per-room scoping, the 32-char cap (reject + exact-max), and `for_room`. ## Operator impact None. No new env var, config, or contract change; no `[operator-action]` marker. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(room): per-room nickname (LC-321)
All checks were successful
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / TruffleHog (push) Successful in 9s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 9s
check-secrets / Kingfisher (pull_request) Successful in 11s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 6m51s
a04f947515
Let a user set a nickname that applies only within one room, overriding their global display name (and username fallback) wherever their messages render in that room. Their username is unchanged, so @mentions, DM deep-links, and avatar initials keep working.

The override folds into the single author-label resolution point: `load_author_meta` (and its wrapper `resolve_msg_author`) gain a `room_id` and, for the real-user branch only, replace `display_name` with the room nickname when one is set. Synthetic actors (webhook/email/bridge) return before that, so they never pick one up. No MessageView shape change; all message renders (page, DM, the three live WS renders, thread/edit) get the nickname for free.

Storage is a new `chat.db::room_nicknames` table (migration 0062, PK room_id+user_id, room_id cascades). The viewer sets/clears their own nickname on the room info docs tab via `POST /room/{id}/nickname` (self-only, room-access-gated, empty = clear); the shared `room/nickname_section.html` partial swaps in place. 32-char cap. en/es parity.

Live-update bound: new and edited messages pick up the nickname immediately; messages already painted in an open tab keep the old label until the next render or reload (a full-backlog re-render is deliberately not attempted).

#LC-321
#LC-322

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-321-per-room-nickname 2026-06-17 01:37:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!337
No description provided.