SRS: UC_4.5.2 — The Pit (Discord Server Widget)
UC_4.5.2: The Pit (Discord Server Widget)
| Field | Value |
|---|---|
| BA in Charge | Anh Hoang |
| Date Created | 2026-08-11 |
| Version | v2.2 (updated 2026-09-06) |
| Document References | Client answer 2026-09-06 — Traders in voice metric — label and formula of the second metric replaced; same widget.json payload, no new endpoint · BA/client answers 2026-08-27 (11 items — QnA Init Docs A-02 → A-13 closed) — endpoint contract confirmed (trimmed DTO, public, no auth); Server_ID + invite URL held as backend env vars, not Table C; both metric labels kept unchanged; instruction block placed inside the card body; 0 renders as 0; [Enter PIT] never changes state; skeleton on first load only; CR-14 common-rule proposal withdrawn; #the-pit announcement routing logged as CR-20260827-001 · RFQ_ Website and Dashboard Implementation V7.pdf (§Community Page Implementation — The Pit) · RFQ_ Stack Trading Prop Tech V7.pdf (§2.3 Discord Bot, §Community Endpoints) · Zapier Integration V7.pdf (Flow 1 Step 6, Flow 7, Flow 38) · Discord Server Details.md (§Channel Specifications, §Compiled Discord Roles and Badges) |
Document References
| # | Original Document | Key Sections Used |
|---|---|---|
| 1 | RFQ_ Website and Dashboard Implementation V7.pdf | §Community Page Implementation — The Pit ("default lounge for all authenticated users"; online count and voice activity metrics) |
| 2 | RFQ_ Stack Trading Prop Tech V7.pdf | §2.3 Discord Bot — link-token flow (/link [UUID] → discord_user_id) · §Community Endpoints |
| 3 | Zapier Integration V7.pdf | Flow 1 Step 6 (link token generation) · Flow 7 (Digital Eviction — POST /discord/revoke-all-roles) · Flow 38 (Digital Concierge — complaint interception in #the-pit / #verify) |
| 4 | Discord Server Details.md | §Channel Specifications — #the-pit, #verify · §Compiled Discord Roles and Badges · §Automated Support Routing and Freshdesk Integration |
| 5 | QnA from clients — STAGE 2: Community & Live Broadcast | Client-confirmed answers on Definition, Business logic, UI/UX, Endpoints (Stage 2 - Dashboard & Fomula (Community).csv, rows C-34 → C-40; Adam 2026-08-03, Adrian Stack 2026-08-05 → 2026-08-10). Includes The Pit / Business logic A9(a)–(b) (Adrian Stack) — server-wide count across all channels incl. Landing Pad; POD Category structure; Voice channels active redefined as a member headcount |
| 6 | QnA from clients — STAGE 2: Settings | Client-confirmed answers on Discord Linking (Stage 2 - Dashboard & Fomula (Settings).csv, rows S-17 → S-22) |
| 7 | CR-20260720-004 | Zendesk → Freshdesk replacement, applied to Flow 38 (CR summary) |
| 8 | BA / client answers, 2026-08-27 (QnA Init Docs — UC_4.5.2, A-02 → A-13) | Closes all eleven items carried by v1. Contract of GET /community/server-status: trimmed DTO {traders_online, voice_channels_active}, public / no auth (the same metrics render on the public website), degraded failure handling. {Server_ID} and the server invite URL are backend environment variables — the Table C proposal is withdrawn, so no schema CR is raised. Labels Traders online and Voice channels active are both kept as drawn (Members online / In voice rejected) under "keep it simple, no scope increases for custom UI changes". Unlinked-account instruction block sits inside the card body, never as a page banner. A fetched 0 renders as 0. Hard Breach frosted glass covers the Command tab only. [Enter PIT] never changes state. Skeleton on first load only, silent thereafter. The CR-14 common-rule generalisation is withdrawn. |
| 9 | Client answer, 2026-09-06 (The Pit — metric alignment) | Second metric renamed to Traders in voice and re-derived from the members array of the same widget.json payload: the count of member objects that carry a channel_id. Traders online continues to map to presence_count. No new Discord endpoint and no backend architecture change — both metrics come from the single widget payload already being fetched. |
1. Overview
| Field | Content |
|---|---|
| ID | UC_4.5.2 |
| Use Case | The Pit (Discord Server Widget) |
| Description | On the Community page, the trader sees a gateway card to the Stack Trading Discord community lounge (#the-pit). The card displays two live server-activity metrics and a single deep-link CTA — [Enter PIT] — that launches the Discord desktop application or web client. For a trader whose Discord account is not yet linked, the card instead renders the mandatory link-token instructions that must be completed before entering the server. The card is a custom UI component, not an embedded Discord Server Widget iframe (Ref: BR_4.5.2.2), and it is identical for SIM (Evaluation) and LIVE (funded) accounts — same access, same metrics, no gating (Ref: BR_4.5.2.1). |
| Zapier Flow | Not triggered by this widget. Context only — Flow 1 Step 6 generates the link token consumed here (Ref:BR_4.5.2.6); Flow 7 (Digital Eviction) and Flow 38 (Digital Concierge) govern the destination Discord server, not this card (Ref: BR_4.5.2.8) |
| Zapier Table | — |
| 3rd Party | Discord — Guild Widget JSON API (https://discord.com/api/guilds/{Server_ID}/widget.json, consumed server-side only) · Discord server invite URL (deep link target) · Discord custom bot (owns the /link slash command) |
References/Wireframe/Stage 2/Community & Live Broadcast/Community (with data).webp The Pit card in its linked state, with both metrics populated.
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 frontend calls
GET /community/server-status. - The 30-second polling interval elapses while the trader remains on the page → the frontend calls
GET /community/server-statusagain (Ref: BR_4.5.2.3).
3. Pre-conditions
- The user is authenticated and is on the Community page. No further condition applies — account type (SIM / LIVE), trader level, and account status are irrelevant (Ref: BR_4.5.2.1).
- The "Server Widget" setting is enabled on the Stack Trading Discord server, so that the guild widget payload is publicly readable by the backend (Ref: BR_4.5.2.3).
4. Post-conditions
- The card renders in exactly one of two account states — Linked (Ref: §5) or Not linked (Ref: §6) — determined solely by whether
discord_user_idis populated on the user record (Ref: BR_4.5.2.7). - The two server-activity metrics are either both displayed with their current values, or both hidden (Ref: BR_4.5.2.5). They are never partially displayed and never show a stale value.
5. Basic Flow — Discord Account Linked
Applies when discord_user_id is populated on the user record.
- The Community page renders. The card displays its section title and the [Enter PIT] action.
- The frontend calls
GET /community/server-status. - The backend serves the response from its Redis cache. On a cache miss, the backend first calls the Discord guild widget API, parses the payload, and stores it in Redis with a 30-second TTL (Ref: BR_4.5.2.3).
- The frontend renders the
Community Chat/Discord Gatewayblock together with the two metrics —Traders onlineandTraders in voice— from the response (Ref: BR_4.5.2.4). The block is display-only; it carries no CTA of its own (Ref: BR_4.5.2.2). - Every 30 seconds the frontend repeats step 2 as a silent background fetch and updates the two values in place. No loading state is rendered on these subsequent fetches (Ref: BR_4.5.2.3).
- The trader clicks [Enter PIT] — the only CTA on the card in this state. The action resolves to the Discord server invite URL and attempts to launch the Discord desktop application, falling back to the Discord web client (Ref: BR_4.5.2.2).
- Discord opens on the server's default landing channel. What the trader can do from there is determined by the Discord roles already held on their account, not by this card (Ref: BR_4.5.2.8).
6. Alternative Flow — Discord Account Not Linked
Applies when discord_user_id is null. This is the default state for a trader who has just purchased an Associate Track and has not yet acted on the onboarding email.
- Steps 1–3 of §5 are unchanged. The section title, the [Enter PIT] action, and both metrics render exactly as in the linked state — the metrics do not depend on the trader's own link status (Ref: BR_4.5.2.4).
- In place of the
Community Chat/Discord Gatewayblock, the card renders the link-token instruction block headedLink your Discord account first(Ref: BR_4.5.2.7), containing three numbered steps and one supporting line:- Step 1 —
Copy your tokenfollowed by the trader's link token, displayed truncated, with a [Copy] action (copy glyph +Copylabel) that copies the full token value to the clipboard and confirms inline (Ref: BR_4.5.2.12). - Step 2 —
Join the serverfollowed by an [Open Discord] action (Discord icon + link label). This is the only place on the card whereOpen Discordappears (Ref: BR_4.5.2.2). - Step 3 —
Type the command /link followed by your token in the #verify channel. - Supporting line —
Complete verification to participate, access exclusive trading rooms, and unlock your badges.
- Step 1 —
- The trader clicks [Copy] → the full token value is copied to the clipboard and the control's label changes to
Copied+ check glyph for 3 seconds, then reverts toCopyon its own (Ref: BR_4.5.2.12). No toast is raised. The token block stays visible; no state on the user record changes. - The trader clicks [Open Discord] (or [Enter PIT]) → Discord opens on the server's default landing channel. Because the trader has not yet submitted the token, they hold only the
everyonerole at this point, which grants read-only access to public channels (Ref: BR_4.5.2.8). - The trader navigates to
#verifyinside Discord and submits/link [token]. The Discord bot matches the token, writesdiscord_user_idto the user record, assigns theAssociate Traderrole, and deletes the token from the database (Ref: BR_4.5.2.6). - On the trader's next load of the Community page, the card renders the linked state (Ref: §5). The link-token instruction block is not rendered again for this account.
7. Exceptional Flow
- [If
GET /community/server-statusfails, times out, or returns no metric values] — both metric rows are hidden entirely. The last known values are not retained, and no—placeholder is rendered. The card shortens to its title, the gateway or token block, and the [Enter PIT] action (Ref: BR_4.5.2.5). - [If a metric returns the value
0] — the value0is displayed. Hiding applies only to a failed fetch, never to a successfully fetched zero (Ref: BR_4.5.2.5). - [If the trader's Discord roles have been revoked by Flow 7 (Digital Eviction)] — the card renders unchanged and remains fully usable. The trader can still open the server, but holds only the
everyonerole there. This card does not detect, display, or act on Discord-side role state (Ref: BR_4.5.2.8). - [If the browser blocks the clipboard write] — the Clipboard API is unavailable (non-secure context) or the browser denies the permission. The control's label stays at
Copy— it never changes toCopied, so the trader is never told a copy succeeded when it did not. The system displays Ref: TE-SYS-01. The token value remains selectable on screen so the trader can copy it manually (Ref: BR_4.5.2.12). - [If the trader purchases a further evaluation while
discord_user_idis already populated] — no new token is generated and the link-token instruction block is not shown again. The backend re-assigns theAssociate Traderrole directly (Ref: BR_4.5.2.6).
8. Business Rules
BR_4.5.2.1: Universal Access — No Account-Type, Level, or Account-Status Gating
The Pit is the default community lounge for all authenticated users. The card renders identically for SIM and LIVE accounts.
- SIM (Evaluation) accounts have full read and write access in
#the-pit. There is no read-only mode, no locked state, and no upsell placeholder for SIM traders on this card. - Read/write access is open to every active trader from Level 1 through Level 24. Only public, unverified visitors to the Discord server are restricted to read-only.
- Trader level does not change access to
#the-pit. Level affects channel visibility elsewhere on the server — higher roles unlock private team channels, priority support routing, and partner areas — but never the lounge itself. - Account risk status does not affect this card. A SIM trader under Soft Breach (Ref: UC_4.10.1), under Hard Breach (Ref: UC_4.10.2), or who has failed the Associate Track still sees and uses the card normally. Breach enforcement restricts trading only, not community access.
- The Hard Breach frosted-glass lock is scoped to the
Commandtab alone. That overlay exists to force the trader into a Reset or Re-buy, so it covers the Command Center and nothing else. TheCommunitypage — this card andMy PODalongside it — stays fully reachable: a breached trader can still read the lounge and watch the broadcast (Ref: UC_4.5.1). Applying the overlay to the Community page is a defect, not a stricter reading of the rule.
BR_4.5.2.2: Custom Card with a Single Deep-Link CTA — Not an Iframe Embed
The Pit is a custom dashboard component. It does not embed the native Discord Server Widget iframe, and it does not host chat or voice inside the dashboard.
- [Enter PIT], in the card header, is the only CTA on the card in the linked state. It resolves to the Discord server invite URL, attempts to launch the Discord desktop application, and falls back to the Discord web client when the application is unavailable.
- The
Community Chat/Discord Gatewayblock is display-only and carries no CTA. - [Open Discord] exists in exactly one place: step 2 of the unlinked-account instruction block (Ref: BR_4.5.2.7), where it is the numbered action of a three-step procedure and must stay in sequence so the trader copies the token before leaving. Its behaviour is identical to
Enter PIT. - No channel-level deep link exists. Every CTA lands the trader on the server's default landing channel, not directly in
#the-pit.
BR_4.5.2.3: Server Activity Metrics — Backend Caching Proxy
Metric data is served exclusively through the backend endpoint GET /community/server-status. Client browsers must never call the Discord API directly.
-
The backend calls the Discord guild widget API
https://discord.com/api/guilds/{Server_ID}/widget.json, parses the payload, and caches the result in Redis with a 30-second TTL. -
{Server_ID}(the Discord Guild ID) and the server invite URL are held as backend environment variables, not as configuration rows. Neither value exists in Table C — Global Variables, and neither is added to it: changing them is a deployment concern, not an Ops task. This is the point of difference from the Live Broadcast widget, whoseActive_Broadcast_URLandBroadcast_Is_Liveare Ops-editable Table C fields (Ref: UC_4.5.1). -
The frontend polls
GET /community/server-statusevery 30 seconds as a silent background fetch. On the first load only, a skeleton occupying the same space as the two metric rows is rendered; every subsequent poll updates the values in place with no spinner, no skeleton and no layout shift. Flashing a loading state twice a minute would interrupt the trader's reading of a card they are not interacting with. -
Effective data freshness is therefore at most 30 seconds, and Discord is called at most once per 30-second window regardless of how many traders have the page open. The proxy exists specifically to prevent Discord from rate-limiting frontend traffic.
-
The "Server Widget" setting must be enabled on the Discord server. No custom bot and no privileged gateway intent is required for these metrics.
-
The aggregation of both metric values happens in the backend, on the cached payload. The frontend renders the two numbers as received and performs no counting or summing of its own.
Endpoint contract.
| Item | Decision |
|---|---|
| Response payload | Trimmed DTO only — { "traders_online": int, "traders_in_voice": int }. The raw widget.json payload is never passed through: it carries the server's member and channel lists, which the card does not use and must not expose to a browser. |
| Authentication | Public — no auth header required. The same metric pair is rendered outside the authenticated Dashboard, on the public website, so gating the endpoint on a session would break that surface. The DTO contains two integers and no member-identifying data, which is what makes a public endpoint acceptable here. |
| Upstream failure | Ref: BR_4.5.2.5. The endpoint degrades — it never propagates a Discord outage into a page-level error. |
- Because the endpoint is public, it is rate-limited by IP at the edge rather than by user identity, and it must not be extended with any user-scoped field later — doing so would silently turn a public endpoint into a data leak.
- Field naming: the DTO keys match the UI labels one-for-one —
traders_online→Traders online,traders_in_voice→Traders in voice(Ref: BR_4.5.2.4). Both carry headcounts of people, so name and value agree and no interpretation note is needed.
BR_4.5.2.4: Metric Definitions and Scope
Both metrics describe the entire Stack Trading Discord server, not the #the-pit channel alone, and neither is filtered by Discord role.
| Metric | Source field | Definition |
|---|---|---|
Traders online | presence_count of the cached widget payload | Count of members currently reporting presence across the whole server, taken from all channels including Landing Pad. Not role-filtered — the value includes staff, moderators, unverified public members, and any bot that reports presence. It is not a count of verified traders. |
Traders in voice | members array of the cached widget payload | Count of member objects in the array that carry a channel_id. A member object holds a channel_id only while that member is connected to a voice channel, so the value is the number of people currently in voice across the whole server. Not role-filtered, and not a count of rooms. |
- Both metrics are headcounts of people, never counts of rooms or channels.
Traders in voiceanswers "how many people are talking right now". Client rationale: "Displaying the number of active rooms does not show community engagement. The dashboard needs to reflect the total number of users actively participating in voice at that moment." - Worked example — 3 voice rooms holding 4, 2 and 1 members renders
Traders in voice 7, not3. - Neither metric displays a denominator or total.
Traders in voicenever renders as7 / 40. - Both values are derived from the same single
widget.jsonpayload already being fetched (Ref: BR_4.5.2.3). No second Discord call, no additional endpoint, and nochannelstraversal is required. #the-pititself is a text channel and has no voice component.Traders in voiceis deliberately server-wide and is not a measure of activity inside#the-pit.- The voice rooms whose occupants are counted are in practice the per-pod rooms. Each POD is provisioned as a Discord Category containing one dedicated text channel and one dedicated voice channel, both visible only to that pod's members — PODs are not voice-only groups (Ref: BR_4.5.2.11).
- Neither metric is role-filtered. A count restricted to the
Stack Trader/Associateroles is out of scope — it cannot be derived from the guild widget payload.
Labels. The two static labels rendered on the card are Traders online and Traders in voice. Both read literally — Traders in voice 7 means seven people are connected to voice. Neither is a room count.
- Both labels are wider than the word "traders" implies: the underlying counts include staff, moderators, unverified public members and any bot reporting presence. The card treats every server member as a trader for display purposes.
BR_4.5.2.5: Metric Failure Handling — Hide, Never Stale
When metric data cannot be obtained, both metric rows are hidden entirely and the card shortens accordingly.
- The last known values must not be retained and re-displayed.
- A
—placeholder, an error message, and a retry action must not be rendered. - The two metrics are hidden and restored together. One metric is never shown while the other is hidden.
- Hiding is triggered by a failed fetch only. A successfully fetched value of
0is displayed as0— for exampleTraders in voice 0outside active trading hours.0is a true reading of a real-time metric and must render; suppressing it would make the card change height on every 30-second poll. - The hide-on-error behaviour is specific to this card and is not a Common Rule. Each widget states its own failure behaviour.
BR_4.5.2.6: Discord Link Token — Single-Use UUID, Never Rotated
The link token displayed on this card is the trader's Discord link token, generated once by Flow 1 Step 6 during onboarding and delivered by onboarding email.
- The token is a single-use UUID. It does not rotate, does not refresh, and does not expire.
- It persists in the database until the trader submits
/link [token]in#verify. On success the bot writesdiscord_user_idto the user record, assigns theAssociate Traderrole, and deletes the token. - One token binds to exactly one Discord account. The first Discord account to submit the token in
#verifyconsumes it and becomes the account written todiscord_user_id. The token is spent at that moment, so a second Discord account submitting the same value is rejected by the bot and no seconddiscord_user_idis ever written for that trader. A trader therefore cannot link two Discord accounts, and two traders cannot share one token. - The binding is not reversible from the dashboard. There is no unlink, re-link or "link a different Discord account" control on this card, and none is built. Correcting a wrong binding is an Ops action on the user record, outside this UC.
- There is no regeneration scenario. If the trader loses the onboarding email, this card displays that same existing token — a new token is never issued.
- The linking action is performed by a Discord slash command, not by a dashboard REST call. The dashboard's only responsibility is to display the token, enable copying it, and instruct the trader where to submit it.
- Once
discord_user_idis populated, the token block is permanently hidden for that account. - If the trader later purchases a further evaluation while
discord_user_idis already populated, token generation is skipped entirely and the backend re-assigns theAssociate Traderrole directly. The trader is never asked to verify a second time.
BR_4.5.2.7: Unlinked-Account CTA — Mandatory Token Guidance Before Leaving for Discord
When discord_user_id is null, the dashboard must guide the trader to copy their link token before letting them leave for Discord. Sending an unlinked trader to the server without their token leaves them on the default everyone role with read-only public access, unable to unlock the paid trader roles they have already bought.
- Placement: the instruction block sits persistently inside the body of The Pit card, replacing the
Community Chat/Discord Gatewayblock, alongside theMy PODwidget on the Community page. It is never rendered as a page-level banner above the Bento Grid — the block is part of the card, not a notice about it. - The block is headed
Link your Discord account firstand carries three numbered steps plus one supporting line. Each step is a short action label followed by its control, so the trader can see what to do without reading a paragraph:
| Step | Label text | Control |
|---|---|---|
| 1 | Copy your token | Truncated token value + [Copy] (icon + Copy label) — behaviour Ref: BR_4.5.2.12 |
| 2 | Join the server | [Open Discord] — Discord icon + link label |
| 3 | Type the command /link followed by your token in the #verify channel. | None — instruction text only |
- Supporting line, below step 3:
Complete verification to participate, access exclusive trading rooms, and unlock your badges.The copy states what the trader gains by verifying. An unverified trader holds onlyeveryone/ read-only public access (Ref: BR_4.5.2.8). - Step 3 spells out the command name (
/link) and the target channel (#verify) in one sentence. It must not be shortened to/link [token]alone — the trader has to know the token is typed after the command, and that the command only works in#verify. - The instruction to use the
#verifychannel is mandatory, not optional — the server invite lands the trader on the default landing channel, and they must navigate to#verifythemselves. - The same link token is also surfaced in Settings. This document owns the Community-page presentation; the Settings presentation has no owning UC in the current WBS and must reference this rule rather than restate it.
BR_4.5.2.8: Discord Permissions Are Server-Side — This Card Does Not Reflect Them
What a trader can do after entering the server is governed entirely by their Discord roles. This card neither reads nor displays that state.
- Any user who follows the invite link is initially assigned the
everyonerole, which grants read-only access to public channels. Write access requires submitting the link token in#verify. - Flow 7 (Digital Eviction) executes
POST /discord/revoke-all-roles, which strips the trader's title and pod roles. It does not kick the trader from the server and does not deletediscord_user_id. Such a trader still passes the linked-state check in §5 and still sees the standard card, but has dropped to read-only public access on the Discord side. - Flow 38 (Digital Concierge) monitors
#the-pitand#verifyfor operational complaint keywords, and on detection deletes the message, posts an automated public reply, and opens a Freshdesk ticket (Ref: CR-20260720-004). This behaviour occurs entirely inside Discord and produces no dashboard-side UI. - The dashboard knows only whether the account is linked — the binary state of
discord_user_idbeing populated ornull. It does not know whether the trader is currently a member of the server, currently online, or currently holds any given role, and it performs no real-time permission check against Discord. [Enter PIT]never changes state. It is not relabelled toRejoin, never carries aJoinedbadge, and is never disabled once the trader has joined. There is no data to drive such a state: a trader who joined and then left the server is indistinguishable, from the Dashboard's side, from one who is still in it. The control stays a permanently active link into the server in every card state.- A Flow 7 revocation is invisible on this card by design. A trader whose
Associate Traderrole was stripped after failing still sees the ordinary card, still clicks through, and lands in the server on read-only public access. The Dashboard shows no badge, banner or warning about the demotion, because it reads the link flag and not the role set. Surfacing it would require a new Discord permission read that is not in scope.
BR_4.5.2.9: No Dashboard Surface for Discord Announcements
Discord announcements — level promotions, referral wins, and similar broadcasts — exist only inside Discord. No dashboard bell notification, toast, badge, or unread indicator is built for them, and this card does not surface them in any form.
Level-up announcements are no longer routed to
#the-pit; that routing change is owned by CR-20260827-001 and changes nothing on this card.
BR_4.5.2.10: Independence from Live Broadcast State
The Pit has no operating hours and no online/offline state. It renders identically whether a broadcast session is live or not, and its 30-second metric polling is unaffected by broadcast state (Ref: UC_4.5.1).
#the-pit and #live-broadcast-chat are two distinct text channels on the same server. #the-pit is the general community lounge and is the destination of this card. #live-broadcast-chat is dedicated to the livestream and feeds the separate chat panel (SIM one-way: Ref: UC_4.9.1; LIVE two-way: Ref: UC_4.14.1, not yet documented in this repo). The two are never merged and never share a message feed.
BR_4.5.2.11: POD Discord Structure — Context for the Voice Metric
This rule records the Discord-side structure whose occupants the Traders in voice metric counts. It is a server-provisioning requirement, not dashboard behaviour, and this card neither creates nor reads it.
- A POD is not a bare voice channel. Each POD is provisioned as a Discord Category containing exactly one dedicated text channel and one dedicated voice channel.
- Both channels are permissioned so that only the members of that specific POD can see and enter them.
- The members connected to those per-pod voice channels are the ones the
Traders in voiceheadcount picks up — each carries achannel_idin the widget payload while connected (Ref: BR_4.5.2.4). - Provisioning itself belongs to the Pod Mentorship module (
POST /create-pod-channel, UC_4.14.2 — not yet documented in this repo) and toDiscord Server Details.md. This UC restates the structure only so the metric formula is unambiguous.
BR_4.5.2.12: Copy Control Behaviour — Inline Copied Confirmation, No Toast
The [Copy] control beside the link token follows the platform's copy-to-clipboard pattern, owned by BR_4.6.1.4 (Settings → Connections & Credentials). It is restated here because this card renders the control in its own unlinked state; the behaviour is identical and must not diverge.
- Copying is a pure client-side action: it calls no API, writes nothing to the database, and changes no account state. It does not consume the token, does not populate
discord_user_id, and does not alter card state (Ref: BR_4.5.2.6). - On click, the control writes the full, untruncated UUID to the clipboard — never the visually truncated display value (Ref: BR_4.5.2.6).
- The control's label changes from
Copy+ copy glyph toCopied+ check glyph for 3 seconds, then reverts toCopyon its own. No further click is needed to reset it. - Clicking the control again while it is in the
Copiedstate re-copies the value and restarts the 3-second timer. - No toast is raised on success. The inline label change is the only confirmation, so repeated copying does not stack notifications.
- This card holds exactly one copy control, so the "only one control in the
Copiedstate at a time" clause of BR_4.6.1.4 has no effect here — it is stated only to confirm the two screens share one pattern. - Failure handling: Ref: §7 Exceptional Flow.
9. Screen Description
| No. | Field Name | Field Type | Displaying rule / Behaviour rule |
|---|---|---|---|
| 1 | Card title | Label | Displaying rule:- Fixed text The PIT. Displayed in every card state.- Overflow: not applicable — fixed-length static text.Behaviour rule:- Read-only. No user interaction. |
| 2 | Enter PIT | Button | Displaying rule:- Displayed in every card state, including when the account is not linked and when both metrics are hidden. Never disabled, never relabelled.- The only CTA on the card in the linked state. Ref: BR_4.5.2.2, BR_4.5.2.8.Behaviour rule:- On click: opens the Discord server invite URL, launching the Discord desktop application and falling back to the Discord web client. Lands on the server's default landing channel. Ref: BR_4.5.2.2. |
| 3 | Discord Gateway block | Label | Displaying rule:- Rendered only when discord_user_id is populated. Contains the fixed text Community Chat and Discord Gateway.- Display-only — contains no CTA. Enter PIT (Row 2) is the sole entry point in this state. Ref: BR_4.5.2.2.- Replaced by Row 5 when discord_user_id is null. Ref: BR_4.5.2.7.- Overflow: Wrap text.Behaviour rule:- Read-only. No user interaction. |
| 4 | Open Discord | Link | Displaying rule:- Rendered only when discord_user_id is null, as the control of step 2 of the instruction block, preceded by the label Join the server. Displays a Discord icon beside the link label.- Not rendered in the linked state. Ref: BR_4.5.2.2.Behaviour rule:- On click: performs the same action as Row 2 — same invite URL, same application-first deep link. Ref: BR_4.5.2.2. |
| 5 | Link-token instruction block | Label | Displaying rule:- Rendered only when discord_user_id is null, replacing Row 3 inside the card body.- Heading: Link your Discord account first.- Three numbered steps: 1 Copy your token + Row 6 + Row 7 · 2 Join the server + Row 4 · 3 Type the command /link followed by your token in the #verify channel.- Supporting line below step 3: Complete verification to participate, access exclusive trading rooms, and unlock your badges.- Overflow: Wrap text. Ref: BR_4.5.2.7.Behaviour rule:- Read-only except for the controls in Row 4 and Row 7.- Disappears permanently once discord_user_id is populated. Ref: BR_4.5.2.6. |
| 6 | Link token | Label | Displaying rule:- Rendered only when discord_user_id is null, as the value of step 1 of the instruction block, preceded by the label Copy your token.- Displays the trader's existing single-use UUID token. Overflow: Truncate, showing the leading and trailing segments of the UUID; the full value is never wrapped across lines.- The same token value is shown on every visit — a new token is never generated. Ref: BR_4.5.2.6.Behaviour rule:- Read-only. The value cannot be edited, regenerated, or requested again from this screen.- The value is selectable so it can be copied manually if the clipboard action is blocked. Ref: §7 Exceptional Flow. |
| 7 | Copy | Button (Text) | Displaying rule:- Rendered only when discord_user_id is null, beside Row 6.- Default label: Copy + copy glyph. Always Enabled while Row 6 is rendered.Behaviour rule:- On click: copies the full, untruncated token value to the clipboard; the label changes to Copied + check glyph for 3 seconds, then reverts to Copy on its own. Clicking again while in the Copied state re-copies and restarts the timer. No success toast. Ref: BR_4.5.2.12.- On clipboard failure: the label stays at Copy and Ref: TE-SYS-01 is displayed. Ref: §7 Exceptional Flow.- Impact: none on stored data — copying does not consume the token, does not change discord_user_id, and does not alter card state. The token is consumed only by the /link slash command inside Discord. Ref: BR_4.5.2.6. |
| 8 | Traders online | Label | Displaying rule:- Label with its numeric value, from presence_count of GET /community/server-status. Server-wide, not role-filtered. Ref: BR_4.5.2.4.- Displayed in both account states.- Hidden entirely, together with Row 9, when the fetch fails. A value of 0 is displayed as 0. Ref: BR_4.5.2.5.- Overflow: not applicable — integer value.Behaviour rule:- Refreshed every 30 seconds by a silent background fetch, updated in place with no loading state after the first load. Ref: BR_4.5.2.3.- Read-only. No user interaction. |
| 9 | Traders in voice | Label | Displaying rule:- Static label text Traders in voice, with its numeric value from GET /community/server-status. The value is the count of members in the widget payload's members array that carry a channel_id — i.e. the number of people currently connected to voice server-wide, not a count of active rooms. No denominator is shown. Ref: BR_4.5.2.4.- Displayed in both account states.- Hidden entirely, together with Row 8, when the fetch fails. A value of 0 is displayed as 0. Ref: BR_4.5.2.5.- Overflow: not applicable — integer value.Behaviour rule:- Refreshed on the same 30-second cadence as Row 8, in the same request. Ref: BR_4.5.2.3.- Read-only. No user interaction. |
Related behaviour owned elsewhere: when the trader navigates into this screen, the Newsquawk audio stream in the persistent header switches to Off automatically. 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 | v2.2 | BR_4.5.2.4 · §9 Row 9 — second metric relabelled Traders in voice | Label Voice channels active, with a table recording that the label reads as a room count while the value is a headcount. | Label is Traders in voice — a static frontend text change. The accepted-imprecision note for this metric is removed: label and value now agree. Traders online is unchanged. | Client answer 2026-09-06. Reverses the 2026-08-27 decision to keep the old label; the In voice wording rejected then is superseded by the client's own new wording. |
| 2026-09-06 | v2.2 | BR_4.5.2.4 · BR_4.5.2.11 · §9 Row 9 — Traders in voice formula | Sum of the members held in the channels array of the cached widget payload. | Count of member objects in the members array that carry a channel_id. Same numeric meaning (people currently in voice), read from a different field of the same payload. | Client answer 2026-09-06, confirmed by BE (Dat Nguyen): same Discord API, "lấy từ members có channel id thay vì lấy channel". |
| 2026-09-06 | v2.2 | BR_4.5.2.3 — data source and DTO key | DTO { traders_online, voice_channels_active }; the key was kept deliberately misaligned with its headcount value to match the old UI label. | Discord source unchanged: same GET https://discord.com/api/guilds/{Server_ID}/widget.json, same 30-second Redis cache, same public GET /community/server-status. No new endpoint and no backend architecture change — both metrics come from the one payload already fetched. DTO key renamed to traders_in_voice; the deliberate-mismatch note is dropped. | Client answer 2026-09-06: "no new API endpoint or backend architecture is required". Fetch unchanged and DTO key rename both confirmed by BE (Dat Nguyen), QnA A-15. |
| 2026-09-06 | v2.2 | BR_4.5.2.4 — members array size | Not previously a factor — the old formula read channels. | No cap applies. The members array is not truncated, so Traders in voice counts the full online population and cannot silently undercount against Traders online. No rule text needed; recorded here only. | BA raised the truncation risk while applying the new formula; BE confirmed no limit, QnA A-16. |
| 2026-09-06 | v2.2 | BR_4.5.2.6 — token ↔ Discord account cardinality | The rule stated the token is single-use and deleted on success, but never said how many Discord accounts one token can bind to, nor what happens to a second submission. FE/BE had no stated behaviour for a re-used token, and QC had nothing to test. | One token binds to exactly one Discord account. The first account to submit it in #verify consumes it and becomes discord_user_id; a second account submitting the same value is rejected by the bot. A trader cannot link two Discord accounts; two traders cannot share one token. The binding is not reversible from the dashboard — no unlink or re-link control exists on this card. | User request 2026-09-06. |
| 2026-08-28 | v2.1 | BR_4.5.2.12 (new) · §6 step 2–3 · §7 · §9 Rows 6–7 — [Copy] feedback state specified | The document stated only that [Copy] "copies the full token value to the clipboard". No confirmation state, no auto-revert, no toast decision and no clipboard-failure branch were specified — the control's post-click behaviour was undefined for FE and untestable for QC. §9 Row 7 typed the control as Button (Icon). | New BR_4.5.2.12 adopts the platform copy pattern owned by BR_4.6.1.4: label → Copied + check glyph for 3 seconds, then auto-reverts to Copy; re-click re-copies and restarts the timer; no success toast; pure client-side, token not consumed. Clipboard blocked → label stays Copy + TE-SYS-01, token value stays selectable for manual copy (new §7 bullet). Row 7 retyped Button (Text) (copy glyph + Copy label, per the design frame and BR_4.5.2.7); Row 6 gains the selectable-value clause. | User request 2026-08-28 (design frames: Copy default state and Copied ✓ confirmed state on The Pit card). No new behaviour is invented — the 3-second inline revert, the no-toast decision and the TE-SYS-01 failure branch are the answers already confirmed for the Settings copy controls (QnA Settings B-09, applied as BR_4.6.1.4); this UC previously left them unstated. The CR-14: Copy-to-Clipboard Control common-rule generalisation raised in QnA B-09 stays withdrawn — the pattern is cross-referenced from BR_4.6.1.4 instead of promoted to Common_rule. |
| 2026-08-27 | v2 | BR_4.5.2.3 — GET /community/server-status contract closed | Response schema and authentication were an open item with BE; the document assumed a trimmed DTO served to authenticated users only, flagged as unconfirmed. | Confirmed contract: trimmed DTO { traders_online, voice_channels_active } only — raw widget.json passthrough is forbidden; endpoint is public, no auth header, because the same metric pair renders on the public website; rate-limited by IP at the edge and must never gain a user-scoped field. Field name voice_channels_active deliberately kept to match the UI label. | BA/client answer 2026-08-27, QnA A-07. Auth decision reverses the BA assumption of authenticated-only. |
| 2026-08-27 | v2 | BR_4.5.2.3 — Server_ID and invite URL storage | Not stated in the document. QnA A-09 proposed adding both to Table C — Global Variables so Ops could edit them without a deploy, and flagged that this would raise a schema CR. | Both values are backend environment variables. Table C holds neither and gains neither. The Table C proposal and its CR are withdrawn — a Guild ID never changes and the invite is a non-expiring link, so there is no Ops-editing case to serve. | BA/client answer 2026-08-27, QnA A-09. Point of difference from UC_4.5.1, whose broadcast fields are Table C rows. |
| 2026-08-27 | v2 | BR_4.5.2.4 — both metric labels closed | Voice channels active carried an open item with the client proposing a rename to In voice; Traders online carried a parallel proposal of Members online in the QnA. | Neither label is renamed. Both keep the wording in the Figma frames and RFQ V7; a table now records the accepted imprecision of each and names the rejected alternative. Formulas unchanged. | BA/client answers 2026-08-27, QnA A-02 and A-13, under the client's standing "keep it simple, no scope increases for custom UI changes". The document, not the label, is the authority on what each number means. |
| 2026-08-27 | v2 | BR_4.5.2.5 — CR-14 withdrawn · conflict recorded | The rule stated hide-on-failure and display-zero. CR-14: Empty/Null Field Container Collapse was proposed in the QnA as a platform-wide Common Rule covering every widget. | CR-14 withdrawn — hide-on-error is per-widget, appropriate for detached third-party embeds (Trustpilot) and wrong for core operational widgets (PnL Gauge, Position Monitor). A 🔴 conflict note records a BA-side proposal to serve stale Redis values on this card instead of hiding, and states that the client's verbatim "If we are unable to fetch this data, hide it" stands until the client reverses it. | BA answer 2026-08-27, QnA A-12. The rule itself is unchanged — a confirmed client answer outranks an internal review. |
| 2026-08-27 | v2 | BR_4.5.2.1 · BR_4.5.2.7 · BR_4.5.2.8 · BR_4.5.2.3 — four BA assumptions confirmed | Placement of the unlinked instruction block, the 0 value, [Enter PIT] state behaviour, breach handling and the loading state were all written as BA proposals awaiting confirmation. | All confirmed as written, with the reasoning now stated: instruction block sits inside the card body (page banner explicitly rejected — it would push the Bento Grid down); Hard Breach frosted glass covers the Command tab alone, Community stays reachable; a Flow 7 role revocation is invisible by design on this card; [Enter PIT] never relabels, badges or disables; skeleton on first load only, silent on every 30-second poll. | BA/client answers 2026-08-27, QnA A-04, A-05, A-06, A-10, A-11. |
| 2026-08-27 | v2 | BR_4.5.2.9 — #the-pit announcement routing logged as a CR | The rule stated only that announcements have no dashboard surface. The contradiction between the client's "Lets not post to #the-pit" and Flow 4's dual-channel post was noted in the QnA as a CR candidate, unlogged. | Logged as CR-20260827-001 and cross-referenced from the rule. | BA/client answer 2026-08-27, QnA A-08 — a behaviour change to a specified flow, not a clarification. No behavioural change in this UC: the card neither reads nor writes announcements; the CR lands on Flow 4 and the Level-Up UC. |
| 2026-08-12 | v1 | BR_4.5.2.2 · §5 step 4/6 · §1 Overview · §9 Rows 2–4 — Open Discord CTA removed from the linked state | Card exposed two CTAs: Enter PIT (header) and Open Discord (inside the Community Chat / Discord Gateway block). Both resolved to the same invite URL and performed the same action. | Enter PIT is the only CTA in the linked state. The Community Chat / Discord Gateway block is display-only. Open Discord survives only as step 2 of the unlinked instruction block, where it is a numbered step of a procedure rather than a duplicate entry point. | Client QnA update 2026-08-12 (BA — Figma design review). Duplicate CTA offering the trader the same outcome twice with no way to distinguish them. Rule text, Screen Description rows and both flows updated together. |
| 2026-08-12 | v1 | BR_4.5.2.7 · §6 step 2 · §9 Row 5 — unlinked-account instruction copy rewritten | Heading Link your Discord account first; three unlabelled ordered steps (token+Copy · Open Discord · Paste /link [token] in the #verify channel); supporting line Without this you'll only get read-only public access. | Same heading; three numbered steps each with an action label — 1 Copy your token + token + [Copy], 2 Join the server + [Open Discord], 3 Type the command /link followed by your token in the #verify channel.; supporting line replaced by Complete verification to participate, access exclusive trading rooms, and unlock your badges. | Client QnA update 2026-08-12. Copy only — the underlying access rule is unchanged (unverified trader still holds everyone / read-only, Ref: BR_4.5.2.8). Supporting line reframed from what the trader loses to what they gain. |
| 2026-08-12 | v1 | BR_4.5.2.4 · BR_4.5.2.3 · §9 Row 9 — Voice channels active formula | Count of voice rooms across the server that currently have at least one member connected. | Total number of members currently connected across all voice channels (sum of members across the voice rooms in the cached widget payload). 3 rooms holding 4 + 2 + 1 members now renders 7, previously 3. | Adrian Stack, The Pit / Business logic A9(b) — "Displaying the number of active rooms does not show community engagement. The dashboard needs to reflect the total number of users actively participating in voice at that moment." Supersedes A5 / A7 and the Endpoints-row mapping. Label rename (In voice) raised as open item A-13; formula stands regardless of the label. |
| 2026-08-12 | v1 | BR_4.5.2.4 Traders online — scope wording | Server-wide presence_count, not role-filtered. | Unchanged formula, now stated explicitly as counting users across all channels including Landing Pad, with no client-side role filtering. | Adrian Stack, The Pit / Business logic A9(a) — clarification, not a behaviour change. |
| 2026-08-12 | v1 | BR_4.5.2.11 — new rule: POD Discord structure | Absent. BR_4.5.2.4 described pods loosely as per-pod voice rooms pod-[leader_name]. | Each POD is a Discord Category containing one dedicated text channel and one dedicated voice channel, permissioned to that pod's members only. PODs are not voice-only groups. | Adrian Stack, The Pit / Business logic A9(b).1. Recorded here as context for the voice headcount; provisioning itself is owned by Pod Mentorship (UC_4.14.2) and Discord Server Details.md. |
| 2026-08-11 | 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-34 → C-40 and Stage 2 - Dashboard & Fomula (Settings).csv rows S-17 → S-22, plus BA decisions recorded in QnA_init_docs.md. Items A-02, A-04 → A-12 were written using the BA/QC proposed positions pending client confirmation; the open response-schema question for GET /community/server-status is flagged inline under BR_4.5.2.3. |