Skip to content

Spec: Project chat — one group chat per Project, with Guests, files and live updates #91

Closed
7 of 7 issues completed

Description

@Oceandek

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

  1. As a Member, I want one chat with my team and our Product Owner, so that we talk where the Project lives.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. As a Member, I want to be refused adding Guests, so that Students can't pull outsiders into a team's chat.
  7. As a Guest, I want to read and write like the team and see the full history, so that I'm a real participant.
  8. As a Guest, I want to leave a chat myself, so that I'm not stuck in it.
  9. As a Product Owner, a Teacher or the Superuser, I want to remove a Guest, so that the chat's people stay right.
  10. 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.
  11. 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

  1. 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.
  2. 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.
  3. 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.
  4. As anyone, I want executable files (.exe, .bat, .sh, …) refused, so that the chat can't spread programs.
  5. 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.
  6. As a Teacher or the Superuser, I want to delete any message, so that I can remove something inappropriate.
  7. As a chat person, I want deleting a message to delete its files too, so that removed content is really gone.
  8. 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.
  9. 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.
  10. 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.
  11. 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.
  12. 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

  1. 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.
  2. As a chat person, I want to see "Anna is typing…" while someone types, so that I know an answer is coming.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. As a Student in one chat, I want the Chat item to open it directly, so that it's one click.
  2. 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.
  3. As a Student without a Project and without Guest chats, I want no Chat item, so that I don't see an empty page.
  4. 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.
  5. 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.
  6. As anyone, I want no emails about chat messages, so that the daily email stays short.

Lifecycle

  1. 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.
  2. 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.
  3. 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.
  4. As a Teacher, I want deleting a Project to delete its chat at once, so that nothing is left behind.
  5. 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.
  6. 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 volume chat-files (like photos).
    • 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.sh adds a chat-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.md gets 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; testing helpers 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

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