SRS: UC_4.9.1 — Discord Chat Stream (ONE-WAY)
UC_4.9.1: Discord Chat Stream (ONE-WAY)
| Field | Value |
|---|---|
| BA in Charge | Anh Hoang |
| Date Created | 2026-08-10 |
| Version | v1.3 |
| Document References | RFQ_ Website and Dashboard Implementation V7.pdf (§Community Page Implementation — Live Chat Feed) · RFQ_ Stack Trading Prop Tech V7.pdf (§2.3 Discord Bot & Message Queueing) · Discord Server Details.md (§#live-broadcast-chat, §Compiled Discord Roles and Badges) |
Document References
| # | Original Document | Key Sections Used |
|---|---|---|
| 1 | RFQ_ Website and Dashboard Implementation V7.pdf | §Community Page Implementation — Live Chat Feed (custom React component; read-only messages streamed to the UI over WebSocket in sync with the live-broadcast Discord channel) |
| 2 | RFQ_ Stack Trading Prop Tech V7.pdf | §2.3 Discord Bot & Message Queueing (dedicated Node.js Discord Bot, Redis memory queue, Batch Worker, Discord rate limit of 5 messages / 5 seconds / channel) |
| 3 | Discord Server Details.md | §#live-broadcast-chat (channel purpose, DISCORD_CHAT_STREAM webhook, User Scopes: Public = Read, Associate Trader and above = Read/Write) · §Compiled Discord Roles and Badges (Associate Trader = Levels 1–5) |
| 4 | QnA from clients — STAGE 2: Community & Live Broadcast | Client-confirmed answers on Definition, Trigger, Business logic, UI/UX, Endpoints (Stage 2 - Dashboard & Fomula (Community).csv, rows C-9 → C-16 and C-34; Adrian Stack / Adam, 2026-08-04 → 2026-08-11). Includes UI/UX A6 + Endpoints A3 — the is_host payload field and the Host name highlight (Adrian Stack, 2026-08-11), and the answers of 2026-09-04 [DCS-BIZ-23] / [DCS-BIZ-24] / [DCS-BIZ-25] in QnA_STAGE2_COMMUNITY_LIVE_BROADCAST.md — append-only stream, media dropped, URLs plain text |
1. Overview
| Field | Content |
|---|---|
| ID | UC_4.9.1 |
| Use Case | Discord Chat Stream (ONE-WAY) |
| Description | On the Community page, next to the livestream player, a custom chat panel streams messages from the Discord #live-broadcast-chat channel into the Dashboard in real time. For a SIM (Evaluation, Level 0) account the panel is ONE-WAY: the trader reads the live feed in full but cannot post, because posting privileges on that channel start at the Associate Trader role (LIVE Level 1+). The message input and the Send control are therefore rendered in a permanently locked state (Ref: BR_4.9.1.2). The panel's open/closed lifecycle is bound to the same is_live flag that drives the broadcast player (Ref: BR_4.9.1.3). The outbound direction (Dashboard → Discord) is not part of this UC (Ref: BR_4.9.1.12). |
| Zapier Flow | — |
| Zapier Table | — |
| 3rd Party | Discord (#live-broadcast-chat channel — message source, server nickname and role metadata, native AutoMod moderation) |
References/Wireframe/Stage 2/Community & Live Broadcast/Community (with data).webp Community page, broadcast Live — chat panel with message feed and the locked input row. Note: the green 38 online pill visible in this wireframe is obsolete and must not be built (Ref: BR_4.9.1.11).
References/Wireframe/Stage 2/Community & Live Broadcast/Community (no data).webp Community page, broadcast Offline — chat panel in the No live chat empty state.
2. Trigger
- The trader navigates to the Community page via the Community tab in the persistent sidebar (Ref: UC_4.1.2 §2 Screen Description) → the chat component mounts and issues its one-time hydration request (Ref: BR_4.9.1.4).
- A new message is posted in the Discord
#live-broadcast-chatchannel → theDISCORD_CHAT_STREAMwebhook pushes it to the Dashboard over WebSocket (Ref: BR_4.9.1.5).
3. Pre-conditions
- The user is authenticated and is on the Community page.
GET /community/live-broadcasthas returned the currentis_livevalue. This endpoint and its contract are owned by UC_4.5.1 and are not redefined here (Ref: BR_4.9.1.3).- The account is a SIM (Evaluation) account at Level 0. No Discord account linking is required to read the feed (Ref: BR_4.9.1.1).
4. Post-conditions
- The panel renders in exactly one of two states: Live — read-only feed with the input row locked, or Offline —
No live chatempty state (Ref: BR_4.9.1.3). - No message originating from the SIM trader is ever transmitted to Discord.
5. Basic Flow — Broadcast Live (SIM read-only)
- The Community page renders.
is_live == trueis returned byGET /community/live-broadcast(Ref: UC_4.5.1 §5), so the chat panel opens together with the video player. - On component mount, the frontend executes a single hydration request and renders the 50 most recent messages held in the backend Redis cache, so that the panel never loads empty (Ref: BR_4.9.1.4).
- The frontend opens the
DISCORD_CHAT_STREAMWebSocket subscription. Every message posted in#live-broadcast-chatarrives as an individual payload{ author, message_text, timestamp, is_host }and is appended to the bottom of the list in real time, one-to-one, with no batching and no delay (Ref: BR_4.9.1.5). The stream carries append events only — an edit or a deletion made afterwards in Discord does not reach the panel (Ref: BR_4.9.1.14). - Each message renders as a bubble showing the author's Discord server nickname with its role suffix, the message body, and the message time (Ref: BR_4.9.1.6, BR_4.9.1.7). The author name is styled from the
is_hostflag on that payload — gold whenis_host == true, standard white otherwise (Ref: BR_4.9.1.13). - A message whose body exceeds four rendered lines is truncated and offered with a
Read morecontrol; expanding it reveals the full body inline, within the panel's fixed height, and exposes a stickyread lesscontrol at the bottom right (Ref: BR_4.9.1.9). - As messages arrive, the list scrolls according to the trader's current scroll anchor — auto-scrolling when anchored at the bottom, or holding position and surfacing a
New Messagesbadge when the trader has scrolled up to read history (Ref: BR_4.9.1.8). - Once the list exceeds 50 messages, the oldest DOM nodes are purged so that the window stays fixed at the 50 most recent messages. There is no pagination and no infinite scroll for older history (Ref: BR_4.9.1.4).
- The message input and the
Sendcontrol remain locked for the entire session. The input cannot receive focus and theSendcontrol cannot be activated, regardless of how long the trader stays on the page and regardless of whether their Discord account is linked (Ref: BR_4.9.1.2).
6. Alternative Flow — Broadcast Offline
Applies whenever is_live == false.
- The chat panel clears the active messages and transitions to the empty state.
- The panel body displays
No live chatwith the supporting lineChat will open when the session goes live. - The input row stays present but locked, and its placeholder changes to
Waiting for session...(Ref: BR_4.9.1.2). - When the broadcast returns to
is_live == true, the panel re-opens and the flow restarts at §5 step 2 — the feed is re-hydrated from Redis; cleared messages are not restored from the client (Ref: BR_4.9.1.3).
7. Exceptional Flow
- [If AutoMod blocks a message before it is published in Discord] — it never enters the WebSocket stream. No message, notice, or placeholder is rendered in the panel; from the Dashboard's perspective the message never existed (Ref: BR_4.9.1.10).
- [If a message is edited or deleted in Discord after it has been broadcast] — the panel keeps the original message unchanged until the rolling window purges it or the broadcast goes offline (Ref: BR_4.9.1.14).
- [If a message carries an image, video, file, GIF, or sticker] — the media is stripped by the backend and only
message_textis rendered. If the message carried no text at all, the backend drops it entirely and nothing is rendered in the panel (Ref: BR_4.9.1.7). - [If a message contains a URL] — it renders as plain, non-clickable text (Ref: BR_4.9.1.15).
- [If a message contains a custom Discord server emoji] — it renders as the raw text string. Standard Unicode emojis render natively (Ref: BR_4.9.1.7).
- [If a message contains an
@mention] — it renders as plain text. No tag is resolved and no notification is raised in the Dashboard (Ref: BR_4.9.1.7). - [If the trader reopens the Community page mid-session] — the panel re-hydrates from the Redis cache with the last 50 messages; it never loads empty while the broadcast is live (Ref: BR_4.9.1.4).
8. Business Rules
BR_4.9.1.1: Feed Visible to All Accounts — Posting Gated by Discord Role
The chat feed itself is universal: #live-broadcast-chat has its public access set to Read-Only, so the stream of messages is globally visible to every authenticated user on the Dashboard, SIM and LIVE alike. There is no hidden state, no locked overlay on the feed, and no upsell placeholder in place of the panel.
Posting is a separate privilege and is gated on the Discord channel's User Scopes (Source: Discord Server Details.md §#live-broadcast-chat):
| Scope | Channel access |
|---|---|
| Public | Read |
Associate Trader and above | Read / Write |
The Associate Trader role starts at Level 1 (Source: Discord Server Details.md §Compiled Discord Roles and Badges — Associate Trader = Levels 1 to 5). A SIM trader is still in the evaluation phase at Level 0 and therefore holds only the Public (Read) scope. This is why the panel is ONE-WAY for SIM.
The lock is derived from the account's level/track, not from the Discord linking state. Linking a Discord account does not raise a SIM trader above Level 0 and therefore never unlocks posting on this panel (Ref: BR_4.9.1.2).
BR_4.9.1.2: Locked Input Row for SIM — Permanent, Single State
For a SIM account the message input and the Send control are rendered in a disabled, locked state. They are not removed from the layout — keeping the row in place preserves the panel height and the page grid.
- The input cannot receive focus and accepts no keystrokes. The
Sendcontrol is disabled and performs no action on click. - Placeholder while the broadcast is live:
Pass the Associate Track to unlock chat - Placeholder while the broadcast is offline:
Waiting for session... - The locked state is permanent for the whole SIM phase. It has exactly one appearance per broadcast state — it does not vary with the trader's Discord linking state, session length, or account risk status.
- Because no message can be composed, the outbound-only rules — the 2,000-character
maxlength, the per-user rate limit and its inline notice, and theEnter/Shift+Entersubmission keys — have no effect in this UC and are not restated here (Ref: BR_4.9.1.12).
BR_4.9.1.3: Chat Lifecycle Bound to the Broadcast is_live Flag
The chat panel opens and closes at the exact same moment as the video broadcast. Both components are driven exclusively by the is_live flag returned from GET /community/live-broadcast. There is no separate schedule, field, or flag that could open the chat earlier or hold it open later than the player.
- The endpoint contract, the call cadence, and the conditions under which the frontend observes a change of
is_liveare owned by BR_4.5.1.3 and are not duplicated here. The chat panel changes state at the same instant the player does, by construction. - On the transition to offline, the panel clears the active messages immediately and switches to the empty state described in §6. Messages are not retained as a read-only archive, and no post-session or intermediate state exists.
- On the transition back to live, the panel re-hydrates from the Redis cache (Ref: BR_4.9.1.4). Cleared messages are never restored from client memory.
BR_4.9.1.4: Hydration and Fixed 50-Message Rolling Window
- Hydration: on initial component mount the frontend executes a single request that loads the 50 most recent messages from the backend Redis cache, so the panel does not load empty.
- Rolling window: the panel maintains a fixed DOM window capped at the 50 most recent messages. Older nodes are purged as new messages arrive, to preserve browser memory.
- No pagination and no infinite scroll. Messages older than the rolling window are not reachable from the Dashboard. Scrolling up within the window is supported and is a read affordance available to SIM traders (Ref: BR_4.9.1.8).
BR_4.9.1.5: Inbound Streaming — Real-Time 1:1 WebSocket, FIFO Ordering
Messages travel from Discord to the Dashboard over the DISCORD_CHAT_STREAM WebSocket in real time, one payload per message. Batching applies exclusively to the outbound direction (Dashboard → Discord) and never to the inbound stream rendered here.
-
The outbound pipeline — a Discord Webhook route with a bot-batching fallback — is owned by BR_4.14.1.3. Neither route touches the inbound contract on this page.
-
⚠️ Open. A message posted through the webhook route carries no Discord role metadata, so
authorandis_hostcannot be read from it. How the backend resolves those two fields is UC_4.14.1 §10 item 15. -
WebSocket payload:
{ "author": "string", "message_text": "string", "timestamp": "ISO8601", "is_host": boolean }Field Type Description authorstring The author's exact Discord server nickname (Ref: BR_4.9.1.6) message_textstring Raw message body, plain text (Ref: BR_4.9.1.7) timestampISO8601 UTC receipt timestamp. Ref: CR-08 is_hostboolean truewhen the author holds the Discord role named exactlyHost, otherwisefalse. Drives the gold name highlight (Ref: BR_4.9.1.13) -
Ordering: messages are sequenced First-In, First-Out on UTC receipt timestamp; display order is guaranteed to match send order. Ref: CR-08.
BR_4.9.1.6: Display Name Composition — Discord Nickname Plus Role Suffix
The name shown on a message bubble is the author's exact Discord server nickname, delivered in the author field of the WebSocket payload. Role designations such as Coach or Mentor are appended automatically from the user's assigned Discord role metadata.
- The backend does not generate custom hashes, anonymised IDs, or separate handle fields. A name such as
Trader 0x4Fis the nickname the user set on Discord, not a system-generated identifier. - A trader cannot set, change, or self-label their display name or avatar from the Dashboard, and there is no chat-identity setting anywhere in the product. A regular trader cannot present themselves as a coach.
- Identity source depends on where the message was typed. A message typed natively in Discord carries the linked Discord profile. A message sent from the LIVE Dashboard goes out through a Discord Webhook whose
usernameandavatar_urlthe backend fills from the Users record, so the name and photo shown in the Discord app are Dashboard-sourced (Ref: BR_4.14.1.12). Either way the values are derived server-side — the trader chooses nothing. - The visual style applied to that name (gold vs. white) is a separate concern and is not derived from the nickname text. It is driven solely by the
is_hostflag on the payload (Ref: BR_4.9.1.13).
BR_4.9.1.7: Content Rendering Constraints — Plain Text and Unicode Emoji Only
The panel is a lightweight text relay, not a Discord client. It renders exactly one field of the payload — message_text — as an unformatted text string.
| Content type | Behaviour |
|---|---|
| Plain text | Rendered as-is |
| Standard Unicode emoji | Rendered natively by the browser. End-to-end rendering is a required test case |
| Custom Discord server emoji | Not supported — arrives as the raw token <:name:id> and renders as that literal string |
| Image, video, file, GIF, sticker | Never rendered. The backend strips attachments[], embeds[] and sticker_items[] from the Discord payload before it is broadcast on DISCORD_CHAT_STREAM. Discord CDN URLs are not proxied, cached or hotlinked |
| URL | Plain, non-clickable text — no anchor tag, no auto-linkify, no rich preview (Ref: BR_4.9.1.15) |
@mention | Rendered as plain text. No auto-complete, no tag resolution, no notification |
| Emoji reaction on a message | Not supported — reactions exist only on the native Discord side and are not synchronised to the Dashboard |
No custom emoji picker is built into the Dashboard; on the LIVE side, composing traders use their operating system's native emoji keyboard.
Media-only message. A Discord message carrying only media arrives with an empty message_text. Once the media is stripped there is nothing left to display, so the backend drops the whole message — it is never broadcast on DISCORD_CHAT_STREAM. The panel renders no empty bubble and no placeholder.
BR_4.9.1.8: Auto-Scroll Anchoring and the New Messages Badge
Standard chat view logic applies, and it is fully available to SIM traders — scrolling and reading are not part of the ONE-WAY restriction (Ref: BR_4.9.1.2).
- [If the trader's scroll position is anchored at the bottom of the container] → the list auto-scrolls downward as new messages arrive.
- [If the trader has scrolled up to read history] → the scroll position is maintained and a small, clickable
New Messagesbadge is displayed at the bottom of the container. Clicking it jumps the list to the newest message and re-anchors the view at the bottom. - The badge is displayed only while the trader is scrolled away from the bottom, and disappears once the view is re-anchored.
BR_4.9.1.9: Long Message — Inline Read more / read less
A message whose rendered body exceeds four lines is truncated in the bubble and offered with a Read more control at the end of the visible text.
Truncation threshold. The limit is 4 rendered lines, applied identically at every breakpoint — it is not a fixed character count.
- The panel width changes between breakpoints, so a fixed character count would produce a different bubble height on every screen. A line clamp keeps the visual weight constant everywhere.
- Message bodies carry Unicode emoji (Ref: BR_4.9.1.7), where one glyph spans several code units, so a character count would also mis-measure the visible length.
- ≈280 characters is the reference equivalent at the desktop panel width (~55–65 characters per line × 4). This figure is for client communication and test-data preparation only — it is not the enforcement mechanism, and no rule may be written against it.
Displaying the Read more control. The control is rendered only when the body is genuinely clipped — measured from the rendered element (its scroll height exceeding its visible height), re-evaluated whenever the panel is resized. A character count must not be used to decide whether the control appears, or a message that fits inside four lines on a wide screen would still be offered a Read more that expands nothing.
Expansion behaviour.
- Activating
Read moreexpands the message inline — it is not a modal and not a side panel — and the full body is revealed. - The chat panel keeps its fixed height; the expanded body is read by scrolling inside the panel. The panel does not grow, the Live Broadcast tile beside it does not resize, and no card below the panel is pushed down. A fixed panel edge is also what makes the
read lessposition workable, since that control is anchored to the panel's bottom right. - One message is expanded at a time. Expanding a second message collapses the first, so several 2,000-character bodies can never occupy the panel at once.
- Once expanded, a
read lesscontrol appears at the bottom right of the chat window and collapses the message back to its truncated form. It is sticky — it stays visible for the whole expanded state, including while the trader scrolls through the middle of a long body. - Expanding a message must not trigger the auto-scroll of BR_4.9.1.8. The top of the expanded message holds its on-screen position, even when the feed was anchored at the bottom at the moment of the click.
- Expansion state is bound to the message and survives the arrival of new messages. If the expanded message is purged by the 50-message rolling window (Ref: BR_4.9.1.4), the state is discarded with it — with no warning and no attempt to retain the message.
- Reading a long message is a read affordance, so SIM traders use
Read more/read lessexactly as LIVE traders do. - A single message can be up to 2,000 characters (the Discord message limit), which is roughly 30 lines at the panel width — the expanded state must render a body of that length within the scrolling panel.
BR_4.9.1.10: Moderation Is Server-Side Only — No Client-Side UI
Content moderation is executed entirely server-side, through Discord's native AutoMod and the backend Discord bot. There is no client-side profanity filter and no custom moderation layer in the Dashboard.
Blocked before publication. AutoMod evaluates a message before it is posted to the channel, so filtered content never reaches DISCORD_CHAT_STREAM. The Dashboard renders no moderation notice, no redacted placeholder and no "message removed" state — such a message simply never appears in the panel.
Deleted after publication. A message a moderator removes by hand once it is already live has already been broadcast. The stream carries no retract event, so the message stays in the panel until the 50-message window purges it or the broadcast goes offline (Ref: BR_4.9.1.14).
That residual visibility is accepted. No retract event is added to the WebSocket and no withdrawal behaviour is built on the frontend.
BR_4.9.1.11: No Online Count on the Chat Panel — Metric Removed
The chat panel displays no online-member count in any state. The green 38 online pill shown in the wireframe is not built: no badge, no metric, no endpoint, and no polling.
The Discord API exposes no active-viewer count for a specific text channel, so the data does not exist at the required granularity. The panel header carries only the static Live chat label.
- This panel only. The
Traders onlinefigure on The PIT card is unaffected — it is a server-wide Discord presence count served byGET /community/server-statusand owned by its own UC.
BR_4.9.1.12: Outbound Boundary — Not Owned by This UC
This UC covers the inbound direction only (Discord → Dashboard). The outbound direction (Dashboard → Discord) is unreachable for a SIM account by definition (Ref: BR_4.9.1.1) and is owned by UC_4.14.1 — Discord Chat Stream (TWO-WAY), LIVE.
The following client-confirmed rules belong to that UC and are referenced here only to mark the boundary — they are not restated as behaviour of this panel: the POST /community/chat/send endpoint and its per-user rate limit of 1 request per second; the inline notice Message rate limit reached. Please wait a moment; the 2,000-character maxlength on the input with no custom character counter; Enter to send and Shift+Enter to insert a line break; and the Redis queue plus Batch Worker aggregation that protects the Discord rate limit of 5 messages per 5 seconds per channel.
Note that BR_4.9.1.13 is not an outbound rule: the Host highlight is applied on the inbound render path and is therefore fully in scope for this UC. A SIM trader sees a Host message rendered in gold exactly as a LIVE trader does.
BR_4.9.1.13: Host Highlight — Gold Author Name Driven by the is_host Flag
A message posted by a Host is visually distinguished so that it stands out in the feed.
Definition of Host. Host is a dedicated Discord role, named exactly Host — nothing else. It is explicitly not:
- the creator or owner of the Discord channel;
- an event-management permission set (invite, mute, kick);
- anything the Dashboard computes, stores, or resolves through an extra API call.
Only a small number of accounts hold the role.
Derivation — Discord bot (backend). When the Node.js Discord Bot reads a new message in the broadcast channel, it evaluates the Discord Roles of the message author:
| Author's Discord roles | is_host |
|---|---|
Contains a role named exactly Host | true |
| Any other role set (including no role at all) | false |
false is the default. The evaluation happens per message, on the backend, before the payload is pushed onto the DISCORD_CHAT_STREAM WebSocket (Ref: BR_4.9.1.5).
Rendering — frontend. The frontend performs no role lookup of its own. It reads the boolean off the incoming payload and applies exactly one of two styles to the author name:
is_host | Author name style |
|---|---|
true | Gold highlight |
false | Standard white text (default) |
- The styling is binary — there is no third style and no per-role palette.
Moderator,Ops,Pod Leader,Associate Traderand every other role render in standard white. - The highlight applies to the author name only. The message body, the timestamp, and the bubble background are unchanged.
- The style is not inferred from the nickname text. A nickname such as
Coach T.in the wireframe renders gold only if that account also holds theHostDiscord role; a trader who renames themselvesCoach ...on Discord gets no highlight (Ref: BR_4.9.1.6). - Hydration from the Redis cache carries
is_hoston each cached message, so the highlight is identical on a re-mounted panel and on live-streamed messages (Ref: BR_4.9.1.4).
Ops config — Stack Trading. The
Hostrole must be created on the Discord server and assigned to the host accounts. Until it exists, every message streams withis_host = falseand the feed renders entirely in white — a valid, non-breaking state. Sotatek builds no part of this.
BR_4.9.1.14: Append-Only Feed — Edits and Deletions in Discord Are Not Synchronised
DISCORD_CHAT_STREAM carries exactly one action: append a read-only message to the bottom of the feed. There is no update event and no retract event.
- The backend runs no Discord gateway listener for
MESSAGE_UPDATEorMESSAGE_DELETE, keeps no message ID for reconciliation, and never mutates or removes a message already rendered. There is no tombstone, no "edited" marker and no deletion animation. - The frontend holds a plain append-only list, bounded by the fixed 50-message window (Ref: BR_4.9.1.4).
- Accepted divergence from Discord: a message edited or deleted in Discord after it was broadcast stays on the Dashboard in its original form until the rolling window purges it, or until the broadcast goes offline and the panel clears (Ref: BR_4.9.1.3). This holds for a hand deletion by a moderator mid-broadcast as well (Ref: BR_4.9.1.10).
BR_4.9.1.15: URLs Render as Plain Text — a Security Control
Every URL in the feed renders as plain, non-clickable text, whatever its origin — typed on the Dashboard or fetched from Discord. The frontend treats message_text as a raw sanitised string: no URL regex, no anchor tag, no link preview, no hover card.
This is a security requirement, not a styling choice. A clickable link inside a financial dashboard is a direct path for a bad actor to place a phishing link on a trader's screen. The stream is read-only text; a trader who needs to open a link shared by the community does so in the native Discord application.
Implementation constraint. "Plain text" must be produced by rendering a text node with <, > and & escaped. dangerouslySetInnerHTML — or any equivalent raw-HTML injection — must not be used, or the sanitised string becomes the XSS surface this rule exists to close.
9. Screen Description
| No. | Field Name | Field Type | Displaying rule / Behaviour rule |
|---|---|---|---|
| 1 | Panel header | Label | Displaying rule:- Static header text Live chat. Displayed in both the Live and the Offline state.- Overflow: Wrap text.Behaviour rule:- Read-only. No user interaction. |
| 2 | Message list | Label | Displaying rule:- Read-only vertical feed of message bubbles, newest at the bottom. Fixed rolling window of the 50 most recent messages; older nodes are purged. No pagination and no infinite scroll. Ref: BR_4.9.1.4.- Populated on mount by a single hydration request from the Redis cache, then appended live over the DISCORD_CHAT_STREAM WebSocket. Ref: BR_4.9.1.5.- Empty state (is_live == false): the list is cleared immediately and the body displays No live chat with the supporting line Chat will open when the session goes live. Ref: BR_4.9.1.3.Behaviour rule:- Scrollable by the trader, including SIM. When anchored at the bottom, auto-scrolls on each new message; when scrolled up, holds position. Ref: BR_4.9.1.8.- Read-only. The trader cannot edit, delete, react to, or reply to any message.- Append-only: a message edited or deleted in Discord afterwards is neither updated nor removed here. Ref: BR_4.9.1.14. |
| 3 | Message bubble | Label | Displaying rule:- Three elements per bubble: author name, message time, message body.- Author name — the Discord server nickname from the author payload field, with the role designation appended automatically from Discord role metadata (for example Coach T.). Overflow: Truncate with tooltip. Ref: BR_4.9.1.6.- Author name style — driven by the is_host boolean on the same payload: is_host == true → gold highlight; is_host == false → standard white text. Binary, applied to the name only; no other role receives a distinct style. Ref: BR_4.9.1.13.- Message time — the message timestamp, displayed in HH:mm. Ref: CR-08.- Message body — plain text plus Unicode emoji, up to 2,000 characters. Custom Discord emojis and @mentions render as plain text. URLs render as plain, non-clickable text — no anchor tag, no auto-linkify, no preview. Images, videos, files, GIFs and stickers are stripped by the backend and never rendered. Ref: BR_4.9.1.15. Overflow: Wrap text, clamped at 4 rendered lines with a Read more control (line clamp at every breakpoint, not a fixed character count; ~280 characters is the desktop reference equivalent only). Ref: BR_4.9.1.7, BR_4.9.1.9.Behaviour rule:- Read-only. No reactions, no reply, no context menu. |
| 4 | Read more / read less | Button (Text) | Displaying rule:- Read more is displayed at the end of the visible text of a truncated message only — that is, only when the rendered body is actually clipped by the 4-line clamp, measured from the element (scroll height > visible height) and re-evaluated on panel resize. Never decided from a character count. Ref: BR_4.9.1.9.- read less is displayed at the bottom right of the chat window, and only while a message is expanded. It is sticky — it stays visible while the trader scrolls through a long expanded body. Ref: BR_4.9.1.9.- Available to SIM traders — expanding a message is a read action, not a post action.Behaviour rule:- On click Read more: expands the message inline and reveals the full body. The chat panel keeps its fixed height — the body is read by scrolling inside the panel; the Live Broadcast tile does not resize and no card below is pushed down. Not a modal and not a side panel.- Only one message is expanded at a time: expanding a second message collapses the first.- Expanding must not trigger the auto-scroll of BR_4.9.1.8 — the top of the expanded message holds its on-screen position.- On click read less: collapses the message back to its truncated form. |
| 5 | New Messages badge | Button (Text) | Displaying rule:- A small badge at the bottom of the message list, label New Messages.- Displayed only while the trader has scrolled up away from the bottom and at least one new message has arrived. Hidden while the view is anchored at the bottom. Ref: BR_4.9.1.8.Behaviour rule:- On click: jumps the list to the newest message and re-anchors the view at the bottom; the badge is then hidden. |
| 6 | Message input | Textbox | Displaying rule:- Default state for a SIM account: Disabled (locked), in both the Live and the Offline state. The row stays in the layout and is never removed. Ref: BR_4.9.1.2.- Placeholder while is_live == true: Pass the Associate Track to unlock chat- Placeholder while is_live == false: Waiting for session...- Overflow: Block input — no characters can be entered in either state.Behaviour rule:- Cannot receive focus and accepts no keystrokes. The state does not change with the trader's Discord linking state, session length, or account risk status. Ref: BR_4.9.1.1. |
| 7 | Send | Button (Primary) | Displaying rule:- Label Send. Default state for a SIM account: Disabled, in both the Live and the Offline state. Ref: BR_4.9.1.2.Behaviour rule:- On click: no action is performed and no request is issued. No SIM message ever reaches Discord. |
Related behaviour owned elsewhere: when the trader navigates into the Community screen, the Newsquawk audio stream in the persistent header switches to Off automatically so that it does not overlap the broadcast audio. That rule is owned by BR_4.1.4.4 and is not restated here.
Changelog
| Date | Version | Updated item | Before | After | Notes |
|---|---|---|---|---|---|
| 2026-09-06 | v1.3 | BR_4.9.1.7 · BR_4.9.1.10 · BR_4.9.1.14 · §7 · editorial pass on BR_4.9.1.5, BR_4.9.1.6, BR_4.9.1.9, BR_4.9.1.11, BR_4.9.1.13 | Two items left open on 2026-09-04: whether a media-only message renders an empty bubble or is dropped, and whether a hand-deleted message remaining on screen mid-broadcast is acceptable for compliance | Media-only message is dropped at the backend — with the media stripped there is nothing to display, so it is never broadcast and no empty bubble or placeholder is rendered · the residual visibility of a hand-deleted message is accepted: no WebSocket retract event and no frontend withdrawal behaviour are built | Both rulings relayed by BA, 2026-09-06. Editorial pass in the same version: verbatim client quotes, inline (Source: …) citations and before/after narration removed from the rule bodies — the rules now state only the settled behaviour, provenance stays in this changelog and in §Document References. The Host role dependency is kept as a one-line ops-config (Stack Trading) note. Closes the two open items raised with v1.2; the same closure applies to UC_4.14.1 §10 item 8, which is now removed from that list |
| 2026-09-04 | v1.2 | New BR_4.9.1.14 · new BR_4.9.1.15 · BR_4.9.1.7 · BR_4.9.1.10 · §5 step 3 · §7 · §9 rows 2–3 | Media handling stated only as "image upload / GIF — not supported"; a URL described only as having "no rich preview", which did not forbid a clickable link; edits and deletions made in Discord not addressed at all; BR_4.9.1.10 read as if every moderated message were dropped before broadcast | The feed is append-only — no MESSAGE_UPDATE / MESSAGE_DELETE listener and no mutation of a rendered message, so a message deleted in Discord stays visible until the 50-message window purges it · the backend strips attachments[], embeds[] and sticker_items[] (image, video, file, GIF, sticker) before broadcasting, with no proxy, cache or hotlink of a Discord CDN URL · URLs render as plain, non-clickable text in both directions, as a security control against phishing, implemented as an escaped text node and never through dangerouslySetInnerHTML | Applies the client answers [DCS-BIZ-23], [DCS-BIZ-24] and [DCS-BIZ-25] (Source: QnA_STAGE2_COMMUNITY_LIVE_BROADCAST.md — Discord Chat Stream, Adrian Stack, 2026-09-04). The client also requires end-to-end testing of Unicode emoji rendering. Not logged as a CR — no folder under References/CR/ carries these answers. Two open items raised: a media-only message leaves an empty bubble (BR_4.9.1.7), and whether a hand-deleted message remaining visible mid-broadcast is acceptable for compliance (BR_4.9.1.10) |
| 2026-09-03 | v1.1 | BR_4.9.1.5 · BR_4.9.1.6 | Outbound batching described as the Batch Worker concatenation of RFQ_ Stack Trading Prop Tech V7.pdf §2.3 · "All chat identities are read from the linked Discord profile" stated without exception | Outbound is now dual-route (Discord Webhook primary, bot batching fallback) and still never touches the inbound stream · carve-out added: for messages sent from the LIVE Dashboard, username and avatar_url come from the Users record, not from the Discord profile | Cross-reference only — no behaviour on this ONE-WAY SIM page changes. Applies CR-20260903-001. One inbound question is now open and does land here: how author and is_host are resolved for a webhook-posted message, which has no Discord role metadata — tracked as UC_4.14.1 §10 item 15 |
| 2026-08-24 | v1 | BR_4.9.1.9 — truncation threshold closed | ⛔ Open item — "the threshold value is deliberately not specified here and will be added once the client confirms"; §5 step 5 and §9 row 3 used the vague phrase "display threshold" | Clamp at 4 rendered lines at every breakpoint (not a character count; ~280 characters is a desktop reference figure only). Read more shown only when the body is measurably clipped (scroll height > visible height), re-evaluated on resize — never from a character count. Expansion keeps the panel's fixed height with internal scrolling; one message expanded at a time; sticky read less; expanding does not fire the BR_4.9.1.8 auto-scroll; expansion state dies with a purged message. | The client deferred the decision to Sotatek ("I'll defer to you"), closing the item left open in [C-12] A17. Line clamp chosen over a character count because the panel width changes per breakpoint (a fixed count yields a different bubble height on every screen) and emoji bodies (Ref: BR_4.9.1.7) mis-measure by character. 4 lines chosen so ordinary chat traffic passes untruncated while a 2,000-character body (~30 lines) cannot swallow the panel. Fixed panel height resolves the client's "makes that chat window grow" against the layout constraint "so the UI grid does not break" ([C-2]) in favour of the latter — consistent with the 2-line title clamp already applied in UC_4.5.1 — and is what makes a control anchored to the panel's bottom right workable. Also closes open items A-06 and A-07 in QnA_init_docs.md. Not yet logged as a CR — the client message carrying the deferral is not in References/CR/. (Source: client deferral relayed by BA, 2026-08-24) |
| 2026-08-11 | v1 | BR_4.9.1.11 — Online member count removed | Online count badge in the panel header: [n] online, per-channel Discord presence, polled ~1 min | No online count in any state — no badge, no metric, no endpoint, no polling | Aligns the UC with the client decision of 2026-08-10, which the initial version had not yet absorbed: Discord's API exposes no active-viewer count for a specific text channel, so the 38 online pill is dropped outright. BR_4.9.1.11 is repurposed as the explicit "do not build" rule (ID kept to preserve existing cross-references). Removed §5 step 8, §6 step 4 and §9 row 2 (Online count badge); §9 rows renumbered 3–8 → 2–7; wireframe caption in §1 annotated as obsolete. Does not affect The Pit's server-wide Traders online. (Source: Stage 2 - Dashboard & Fomula (Community).csv — Discord Chat Stream / Business logic A19 + UI/UX A5, Adrian Stack, 2026-08-10) |
| 2026-08-11 | v1 | BR_4.9.1.13 — new rule | — | Host Highlight — gold author name driven by the is_host flag | Client closed the open item on UI/UX A4 ("Coach vs Host — same role or two?"): the highlight is driven by one dedicated Discord role named exactly Host, evaluated by the Discord bot per message. Not a channel-owner concept, not an event-permission set, no extra API call. Flags that the Host role is still absent from Discord Server Details.md. (Source: Stage 2 - Dashboard & Fomula (Community).csv — Discord Chat Stream / UI/UX A6, Adrian Stack, 2026-08-11) |
| 2026-08-11 | v1 | BR_4.9.1.5 — WebSocket payload | { author, message_text, timestamp } | { author, message_text, timestamp, is_host } | New boolean is_host added to the DISCORD_CHAT_STREAM payload, set by the Discord bot from the author's Discord roles. Also propagated to §5 step 3 / step 4, BR_4.9.1.6, and §9 row 3 (Message bubble). (Source: same file — Discord Chat Stream / Endpoints A3, Adrian Stack, 2026-08-11) |
| 2026-08-10 | v1 | Initial document | — | — | Created via Init Flow (Auditor → Challenger → Architect). Content sourced from client-confirmed answers in Stage 2 - Dashboard & Fomula (Community).csv rows C-9 → C-16 and C-34, plus Discord Server Details.md and the approved SIM wireframes. UC_ID assigned by BA on 2026-08-10 (Soft Breach renumbered to UC_4.10.1). BR_4.9.1.9 truncation threshold left open pending client. |