Repository navigation
Spec: Project chat — one group chat per Project, with Guests, files and live updates #91
Copy link
Copy link
Closed
7 / 77 of 7 issues completedClosed
7 / 77 of 7 issues completed
Copy link
Description
Problem Statement
Teams talk in WhatsApp groups, Teams chats and email threads that the platform doesn't know about. When a Member is removed or moved, they keep reading their old team's group; new Members miss what was decided before they joined; Product Owners and coaches are in some groups and not in others; and photos and files shared there disappear with the phone. Teachers have no place to follow or moderate what a team discusses, and when a Project ends, its conversations are scattered and never cleaned up.
Solution
One group chat per Project, inside the platform:
- Who: the Project's Members, its Product Owner, and Guests: any active User added by the Product Owner, a Teacher or the Superuser. Every Teacher and the Superuser can also read, write and moderate every chat.
- Messages: text (max 4000 characters, clickable links), photos and files (max 5 per message, 10 MB each), reply with a quote, emoji reactions (👍 ❤️ 😂 😮 😢 🎉), @mentions (people and @team), edit and delete your own messages, staff moderation.
- Live: new messages and "is typing…" appear within a few seconds without reloading; unread badge in the navigation; "Seen by" on your own messages; search inside a chat.
- Where: a Chat item for Students (straight into their chat, or a list when they're also a Guest elsewhere); a Chats screen for Teachers and the Superuser with unread counts and an "Only my chats" filter; a Chat button on a Project's page. No emails.
- Lifecycle: an Archived Project's chat becomes read-only (staff only) and is deleted 6 months after archiving; deleting a Project deletes its chat; messages of deleted Users stay as "Deleted user".
Decided in a grilling session on 2026-10-22 (Q1-Q22).
User Stories
Who is in a chat
- As a Member, I want one chat with my team and our Product Owner, so that we talk where the Project lives.
- As a Product Owner, I want to be in the chat of every Project I own, so that I can follow and answer my teams.
- As a Member who joins a Project (added or moved in), I want to see the chat's full history, so that I can catch up on what was decided.
- As a Member who is removed or moved out, I lose access to the old chat at once (my messages stay, under my name), so that a team's chat stays with that team.
- As a Product Owner, a Teacher or the Superuser, I want to add any active User to a Project's chat as a Guest (e.g. a Student from another Project), so that people outside the team can join the conversation.
- As a Member, I want to be refused adding Guests, so that Students can't pull outsiders into a team's chat.
- As a Guest, I want to read and write like the team and see the full history, so that I'm a real participant.
- As a Guest, I want to leave a chat myself, so that I'm not stuck in it.
- As a Product Owner, a Teacher or the Superuser, I want to remove a Guest, so that the chat's people stay right.
- As anyone in a chat, I want a people list showing Members, the Product Owner and Guests (marked "Guest"), so that I know who reads along.
- As a Teacher or the Superuser, I want to read and write in every Project's chat, so that I can follow and help any team.
Messages
- As a chat person, I want to send text messages of up to 4000 characters, with web links clickable, so that we can discuss and share links.
- As a chat person, I want to attach up to 5 photos or files (10 MB each) to a message, so that we can share pictures, datasheets and documents.
- As a chat person, I want photos (jpg, png, webp, gif) shown as thumbnails that open full size, and other files as a download with their name and size, so that I see what was shared.
- As anyone, I want executable files (.exe, .bat, .sh, …) refused, so that the chat can't spread programs.
- As a chat person, I want to edit my own messages (shown "(edited)") and delete them (shown "This message was deleted."), so that I can fix mistakes without rewriting history silently.
- As a Teacher or the Superuser, I want to delete any message, so that I can remove something inappropriate.
- As a chat person, I want deleting a message to delete its files too, so that removed content is really gone.
- As a chat person, I want to reply to one message with a quote above my reply, and click the quote to scroll to the original, so that answers stay clear in a busy chat.
- As a chat person, I want a quote of a deleted message to say "This message was deleted.", so that deleted content doesn't live on in replies.
- As a chat person, I want to react with 👍 ❤️ 😂 😮 😢 🎉 (several per message, click again to remove), with a count and the names on hover, so that I can answer quickly.
- As a chat person, I want to type @ and pick a chat person (or @team for all Members), so that I can address someone directly.
- As a mentioned person, I want the message highlighted and a red @ marker on the chat until I've read it, so that I don't miss what's meant for me.
Live, unread and seen
- As anyone with an open chat, I want new messages, edits, deletions and reactions to appear within a few seconds without reloading, so that the chat feels live.
- As a chat person, I want to see "Anna is typing…" while someone types, so that I know an answer is coming.
- As a Student, I want a badge with my number of unread chat messages on the Chat item, so that I know when there's something new.
- As a Teacher, I want my badge to count only "my chats" (Projects where I'm the Product Owner or a Guest), so that the number stays meaningful.
- As the author of a message, I want to see "Seen by 3" with the names on click, counting only the chat's people (not staff who only read along), so that I know my team read it.
- As a chat person, I want to search a chat's messages and file names, filter by person and "Files only", and jump to a result, so that I find things back.
Screens
- As a Student in one chat, I want the Chat item to open it directly, so that it's one click.
- As a Student who is also a Guest elsewhere, I want the Chat item to open a list of my chats with unread counts, so that I can choose.
- As a Student without a Project and without Guest chats, I want no Chat item, so that I don't see an empty page.
- As a Teacher or the Superuser, I want a Chats screen listing chats with unread counts, @ markers and an "Only my chats" filter (on by default), so that I find the chats that matter to me and can open any other.
- As a Teacher or the Superuser, I want a Chat button on a Project's page, so that I go from a Project to its chat.
- As anyone, I want no emails about chat messages, so that the daily email stays short.
Lifecycle
- As a Teacher, I want an Archived Project's chat kept read-only and readable only by Teachers and the Superuser, so that history stays as it was and former Members and Guests no longer read along.
- As a Teacher, I want the chat to show "This chat will be deleted on " and be deleted with its files 6 months after archiving, so that storage doesn't grow forever.
- As a Teacher, I want restoring a Project within those 6 months to bring the chat back writable with its history, so that a Project can continue.
- As a Teacher, I want deleting a Project to delete its chat at once, so that nothing is left behind.
- As the Superuser, I want to delete a User who has written messages; their messages stay, shown as "Deleted user", so that one chat message never blocks deleting a test or duplicate account.
- As anyone, I want a deactivated User's messages to stay under their name, so that the conversation stays readable.
Implementation Decisions
- Chat people of a Project: its Members, its Product Owner and its Guests. Staff (every Teacher and the Superuser) can always read, write and moderate; only chat people count for the badge, @mentions, "Seen by" and typing. Students who are not chat people are refused (403).
- Data (new migrations, from
0014):chat_guests: (project_id, user_id) primary key, added_by, added_at; cascade on Project and User delete.chat_messages: id, project_id (cascade), author_id (nullable, SET NULL on User delete: shown "Deleted user"), body (≤ 4000), reply_to_id (SET NULL), created_at, edited_at, deleted_at (soft delete: body and files removed, row kept so quotes say "This message was deleted.").chat_files: id, message_id (cascade), name, size, content_type, stored on a new volumechat-files(likephotos).chat_reactions: (message_id, user_id, emoji) primary key; emoji limited to the six.chat_mentions: (message_id, user_id); @team stored as mentions of the Members at send time.chat_reads: (project_id, user_id) primary key, last_read_message_id: drives unread counts, @ markers and "Seen by".projects.archived_at(set on archive, cleared on restore) for the 6-month cleanup.
- API: REST under
/api/projects/{id}/chat/...and/api/chats,/api/chat-files/{id}; the frontend stays the only public part (ADR 0006). - Live updates by polling (new ADR 0008): an open chat asks the backend every ~2 s for changes since its last known state (new/edited/deleted messages, reactions, typing, reads); the navigation badge refreshes every ~10 s. No WebSocket between frontend and backend, in line with ADR 0003. "Is typing…" is kept in the backend's memory (single process, like
LoginGuard) and expires after ~5 s. - Files: uploaded through the frontend to the backend (multipart), checked for size (10 MB), count (5) and refused extensions; images get a thumbnail; served through a frontend route that proxies with the user's token (like
/photos/{id}).backup.shadds achat-files-<ts>.tar.gz; the restore test checks it. - Cleanup: the backend's existing background loop (the one sending the daily email) deletes, once a day, the messages and files of Projects archived more than 6 months ago.
- Glossary:
CONTEXT.mdgets Chat, Chat people, Guest, Unread, Mention.
Testing Decisions
- API tests per ticket for access (Member, Product Owner, Guest, staff, outsider Student, removed Member), validation and every rule above;
testinghelpers for chats like the existing ones. - NiceGUI tests for the screens (send, see the badge, add a Guest, upload a photo, react, mention, search), waiting for unique markers as in the other frontend tests.
- The 6-month cleanup is tested with a moved clock (DB
now()comparisons as for the Top 3 round). - The restore test after every deploy also restores the chat-files archive.
Out of Scope
- A general channel for everyone and direct messages between two people.
- Emails or push notifications for chat messages or mentions.
- Searching across all chats; real threads in a side panel.
- Formatting (bold, lists, code blocks); emoji other than the six reactions.
- Voice and video messages.
Metadata
Metadata
Assignees
Labels
No labels
Activity
Chat lifecycle: archive, 6-month cleanup, restore, deleted Users (#98) (
Oceandek commentedon Oct 29, 2026
All tickets done (#92–#98, PRs #99, #101, #102, #104, #105, #106, #107); deployed 2026-10-29 13:18.