-
Notifications
You must be signed in to change notification settings - Fork 113
Implemented Audience Interaction with Live Polls, Anonymous Q&A #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implemented Audience Interaction with Live Polls, Anonymous Q&A #115
Conversation
- Added functionality to create polls via WebSocket with appropriate payload handling. - Introduced new data structures for poll metadata and state management. - Enhanced the `loadPollSnapshot` function to include poll metadata in the snapshot. - Updated WebSocket message handling to support poll creation and voting events. - Improved frontend components to display audience polls and comments in real-time. - Refactored related atoms and hooks to manage poll state effectively.
WalkthroughAdds a spectator WebSocket hub, Redis-backed poll storage, an event schema for debate interactions, team token-bucket turn management, and multiple frontend spectator/room UI updates with signaling and RTC integration for live debates. Changes
Sequence Diagram(s)sequenceDiagram
participant Spectator as Spectator (Browser)
participant WS as WebSocket Server
participant Hub as DebateHub
participant PollStore as PollStore (Redis)
Spectator->>WS: HTTP upgrade -> DebateWebsocketHandler
WS->>Hub: Register(debateID, conn, spectatorHash)
Hub->>Hub: ensure DebateRoom exists
WS->>PollStore: loadPollSnapshot(debateID)
PollStore-->>WS: poll state snapshot
WS->>Spectator: send initial snapshot + presence
Spectator->>WS: send "vote"/"createPoll"/"question"/"reaction"
WS->>WS: handleVote/handleCreatePoll/handleQuestion/handleReaction
WS->>PollStore: (Vote/CreatePoll) persist
PollStore-->>WS: success/failure
WS->>Hub: BroadcastToDebate(debateID, event)
Hub->>Spectator: event broadcast to all connected spectators
sequenceDiagram
participant TeamMember as Team Member (Browser)
participant WS as WebSocket Server
participant TBS as TokenBucketService
participant TTM as TeamTurnManager
participant MongoDB as MongoDB
TeamMember->>WS: connect -> TeamWebsocketHandler
WS->>TBS: InitializeTeamBuckets(teamID)
TBS->>MongoDB: fetch team members
WS->>TTM: InitializeTeamTurns(teamID)
TTM->>MongoDB: fetch team members
TeamMember->>WS: "attempt to speak" / request
WS->>TBS: TryConsumeForSpeaking(teamID, userID, ttm)
TBS->>TTM: Check current turn
TTM-->>TBS: isCurrentTurn
TBS-->>WS: result + remaining tokens
WS->>TeamMember: broadcast speaking status / grant
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 38
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
frontend/src/hooks/useUser.ts (1)
45-46: Restore error reporting in fetch failure path.
The catch now swallows all errors silently, leaving auth/profile fetch failures invisible and making user-state issues nearly impossible to debug. Please put the logging (or comparable handling) back so we surface these failures.- } catch (error) { - } + } catch (error) { + console.error('Failed to fetch user profile', error); + }frontend/src/Pages/DebateRoom.tsx (1)
509-510: Provide user feedback when bot turn fails.The catch block silently swallows errors without informing the user. When
sendDebateMessagefails, the user sees no bot response and no explanation, making it appear as if the bot simply skipped its turn.Consider adding user-visible error feedback (e.g., a popup message like "Bot failed to respond, moving to next turn") so users understand what happened rather than wondering if the app is broken.
Apply this diff to add user feedback:
} catch (error) { + setPopup({ + show: true, + message: "Bot failed to respond. Moving to next turn.", + }); + setTimeout(() => setPopup({ show: false, message: "" }), 2000); setNextTurnPending(true); } finally {frontend/src/components/Matchmaking.tsx (1)
54-135: Restore automatic reconnect after WebSocket closes.With the new “already connected” guard, the effect that opens the socket never runs again once the initial connection closes (server restart, network blip,
room_creatednavigation, etc.). Afterws.onclosefires,isConnectedtoggles, but none of the hook’s dependencies change, so no reconnect happens—wsRef.currentstays locked to a CLOSED socket and all matchmaking actions start throwing “Connection is not ready”.Please either include a reconnection trigger (e.g., reset
wsRef.currentand let the effect rerun, addisConnectedto the dependency list, or schedule a reconnect fromonclose) so the UI recovers automatically instead of forcing a full page reload.backend/controllers/debatevsbot_controller.go (3)
162-163: Database save errors are silently ignored.The error from
SaveDebateVsBotis checked but not handled, which could lead to data loss without any indication. Errors from database operations should be logged at minimum, or returned to the caller.if err := db.SaveDebateVsBot(debate); err != nil { + log.Printf("Failed to save debate: %v", err) + // Consider whether to return error to client }
207-208: Database update errors are silently ignored.The error from
UpdateDebateVsBotOutcomeis checked but not handled. This could result in incorrect debate outcomes being persisted without any error indication.if err := db.UpdateDebateVsBotOutcome(email, result); err != nil { + log.Printf("Failed to update debate outcome: %v", err) }
266-275: Transcript save errors are deliberately discarded.The blank identifier explicitly discards the error from
SaveDebateTranscript. This prevents detection of transcript persistence failures, which could impact users expecting to review their debates later.- _ = services.SaveDebateTranscript( + if err := services.SaveDebateTranscript( userID, email, "user_vs_bot", latestDebate.Topic, latestDebate.BotName, resultStatus, req.History, nil, - ) + ); err != nil { + log.Printf("Failed to save debate transcript: %v", err) + }backend/controllers/auth.go (1)
500-531: Use the sanitized user payload inVerifyToken
VerifyTokenmarshals themodels.Userfields directly, so any stored NaN/Inf ratings will make Gin’s JSON encoding fail withjson: unsupported value: NaN, returning a blank response instead of a token check result.(gigi.nullneuron.net) Reuse the existing normalization flow before emitting the response and render the sharedbuildUserResponsemap (with sanitized floats) to keep the payload safe and consistent.@@ - // Return user details - ctx.JSON(200, gin.H{ - "message": "Token is valid", - "user": gin.H{ - "id": user.ID.Hex(), - "email": user.Email, - "displayName": user.DisplayName, - "nickname": user.Nickname, - "bio": user.Bio, - "rating": user.Rating, - "rd": user.RD, - "volatility": user.Volatility, - "lastRatingUpdate": user.LastRatingUpdate.Format(time.RFC3339), - "avatarUrl": user.AvatarURL, - "twitter": user.Twitter, - "instagram": user.Instagram, - "linkedin": user.LinkedIn, - "isVerified": user.IsVerified, - "createdAt": user.CreatedAt.Format(time.RFC3339), - "updatedAt": user.UpdatedAt.Format(time.RFC3339), - }, - }) + if normalizeUserStats(&user) { + if err := persistUserStats(dbCtx, &user); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to refresh user stats"}) + return + } + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "Token is valid", + "user": buildUserResponse(user), + })frontend/src/Pages/OnlineDebateRoom.tsx (1)
473-1014: Parameterize the backend host; localhost hard-coding breaks real usersNew logic is scattering
http://localhost:1313/...andws://localhost:1313/...(judgment polling, room socket). Once deployed, browsers will attempt to talk to their own localhost and the whole flow fails. Please derive both the WebSocket and REST URLs from the configured API base instead of hard-coding localhost.Illustrative fix:
- const wsUrl = `ws://localhost:1313/ws?room=${roomId}&token=${token}`; + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const apiHost = + import.meta.env.VITE_API_URL?.replace(/^https?:\/\//, "") ?? + window.location.host; + const wsUrl = `${protocol}//${apiHost}/ws?room=${roomId}&token=${token}`; @@ - const pollResponse = await fetch( - `http://localhost:1313/submit-transcripts`, + const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; + const pollResponse = await fetch( + `${baseUrl}/submit-transcripts`, @@ - const response = await fetch(`http://localhost:1313/submit-transcripts`, { + const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; + const response = await fetch(`${baseUrl}/submit-transcripts`, {Apply the same substitution anywhere else we call back into the debate API in this file.
🧹 Nitpick comments (18)
frontend/tsconfig.app.tsbuildinfo (1)
1-1: Consider excluding.tsbuildinfofrom version control.The
tsconfig.app.tsbuildinfofile is a TypeScript build artifact generated during compilation and used for incremental build optimization. It typically shouldn't be committed to version control, as it can cause unnecessary merge conflicts and increase repository size without providing value to developers.Ensure this file is in
.gitignore:# Add to frontend/.gitignore (or root .gitignore if it covers frontend) *.tsbuildinfoAlternatively, if there's a specific organizational reason for tracking this artifact, document that decision.
backend/utils/user.go (1)
3-12: Reorganize imports per Go conventions.Imports should be organized into groups: standard library, third-party, then project packages. The current order is fragmented (project → standard lib → third-party → standard lib).
Apply this diff to reorganize imports correctly:
import ( + "context" + "strings" + "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" + "arguehub/db" "arguehub/models" - "context" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "strings" - "time" )frontend/src/components/PlayerCard.tsx (1)
32-38: Consider making player details configurable.The player name ("Sofia Dev"), rating ("1000"), and avatar image are currently hardcoded. Consider adding these as props to make the component more reusable across different contexts.
interface PlayerCardProps { isUser: boolean; cameraOn?: boolean; micOn?: boolean; setCameraOn?: React.Dispatch<React.SetStateAction<boolean>>; setMicOn?: React.Dispatch<React.SetStateAction<boolean>>; isTurn: boolean; turnDuration?: number; + playerName?: string; + playerRating?: number; + avatarSrc?: string; } const PlayerCard: React.FC<PlayerCardProps> = ({ isUser, cameraOn = true, micOn = true, setCameraOn, setMicOn, isTurn, turnDuration = 0, + playerName = "Sofia Dev", + playerRating = 1000, + avatarSrc = avatar1, }) => { return ( <div className="flex justify-between items-center border rounded w-full p-2 "> {/* Player Details Section */} <div className="flex items-center"> <Avatar className="bg-primary mx-2"> - <AvatarImage src={avatar1} alt="@name" className="object-cover" /> + <AvatarImage src={avatarSrc} alt={`@${playerName}`} className="object-cover" /> - <AvatarFallback>SL</AvatarFallback> + <AvatarFallback>{playerName.split(' ').map(n => n[0]).join('')}</AvatarFallback> </Avatar> - <span>Sofia Dev</span> + <span>{playerName}</span> <Separator orientation="vertical" className="mx-2 h-5 bg-muted" /> - <span>1000</span> + <span>{playerRating}</span> </div>backend/routes/pros_cons_route copy.go (1)
10-21: Hard-coded skill level reduces flexibility.The function uses a hard-coded
"intermediate"skill level (line 13) rather than accepting it as a parameter. The comment claims a "default," but the implementation is inflexible. This may be intentional, but consider whetherGetProsConsTopicshould accept an optional skill level parameter or whether the skill level should come from user context.Consider one of these approaches:
Option A (parameter-based): Accept skill level as a query parameter:
skillLevel := c.DefaultQuery("skillLevel", "intermediate")Option B (context-based): Extract skill level from authenticated user context or session.
Which approach aligns with your application design?
backend/rating/glicko2.go (1)
78-204: Optional: Consider separating formatting from functional changes.The blank lines added throughout improve readability by separating logical sections. However, mixing formatting changes with functional changes can make it harder to review the substantive modifications.
frontend/src/Pages/TournamentHub.tsx (1)
210-254: Consider aligning validation strategy for consistency.The addition of
requiredattributes (lines 219, 232) is a good UX improvement. However, the validation approach is inconsistent:
- Name field (line 219): has both HTML5
requiredand JS validation (lines 55-58)- Date field (line 232): has HTML5
requiredbut no JS validation- Description field: has neither
Consider one of these approaches for consistency:
Rely on HTML5 validation: Remove the redundant JS validation for name (lines 55-58) since the
requiredattribute will catch empty values before form submission.Add comprehensive JS validation: Add validation for the date field to match the name validation pattern, ensuring a consistent error messaging strategy.
Example for approach 2:
const handleCreate = (e: FormEvent) => { e.preventDefault(); if (!name) { setError("Tournament name is required."); return; } + if (!date) { + setError("Tournament date is required."); + return; + }frontend/src/Pages/DebateRoom.tsx (2)
252-285: Consider removing the redundant guard check.The
if (SpeechRecognition)check at line 252 is redundant since the outer condition at line 249 already verifies thatSpeechRecognitionorwebkitSpeechRecognitionexists in the window. If either exists, the assignment at lines 250-251 will always succeed with a truthy value.Apply this diff to simplify:
if ("SpeechRecognition" in window || "webkitSpeechRecognition" in window) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; - if (SpeechRecognition) { - recognitionRef.current = new SpeechRecognition(); - recognitionRef.current.continuous = true; - recognitionRef.current.interimResults = true; - recognitionRef.current.lang = "en-US"; + recognitionRef.current = new SpeechRecognition(); + recognitionRef.current.continuous = true; + recognitionRef.current.interimResults = true; + recognitionRef.current.lang = "en-US"; - recognitionRef.current.onresult = (event) => { + recognitionRef.current.onresult = (event) => { let newFinalTranscript = ""; let newInterimTranscript = ""; for (let i = event.resultIndex; i < event.results.length; i++) { @@ -274,14 +271,13 @@ } else { setInterimInput(newInterimTranscript); } - }; + }; - recognitionRef.current.onend = () => setIsRecognizing(false); - recognitionRef.current.onerror = () => { - setIsRecognizing(false); - }; - } + recognitionRef.current.onend = () => setIsRecognizing(false); + recognitionRef.current.onerror = () => { + setIsRecognizing(false); + }; }
282-283: Restore error logging for observability.Removing all
console.errorstatements from error handlers (speech recognition errors at lines 282-283, bot turn failures at lines 509-510, and judging failures in the catch block around lines 527-555) makes production debugging challenging. Without error logs, you cannot diagnose failure patterns or root causes when users report issues.Consider restoring minimal error logging in catch blocks:
recognitionRef.current.onerror = () => { + console.error("Speech recognition error occurred"); setIsRecognizing(false); };} catch (error) { + console.error("Bot turn failed:", error); setNextTurnPending(true); } finally {} catch (error) { + console.error("Debate judging failed:", error); setJudgmentData({Also applies to: 509-510, 527-555
backend/models/user.go (1)
16-18: Inconsistent field alignment.These three fields have additional leading spaces, but the rest of the struct fields (lines 11-15, 19-29) do not. Consider using
gofmtto ensure consistent formatting across the entire struct, or align all fields uniformly if manual alignment is intended.backend/test_server.go (1)
1-50: Consider retaining logging in test/debug scripts.All logging statements have been removed from this test server. While this aligns with the broader PR pattern of reducing console output, test scripts typically benefit from logging to verify behavior and diagnose issues. Without any output, it's difficult to confirm whether the matchmaking test is working as expected or to debug failures.
Consider keeping at least minimal logging for test/debug scripts, or rename/relocate this file if it's no longer intended for testing purposes.
frontend/src/Pages/StrengthenArgument.tsx (1)
101-143: Consider conditional logging for development environments.Console error logging was removed from the error handlers (lines 134-143 in
fetchWeakStatementand similar inhandleSubmit). While user-facing error notifications remain vianotify(), removing console logs can make development debugging more difficult.Consider using conditional logging that only outputs to console in development:
if (import.meta.env.DEV) { console.error('Error fetching weak statement:', err); }This preserves debugging capability during development while keeping production console output clean.
frontend/src/components/Chatbox.tsx (1)
120-123: Preserve speech recognition error detailsWith the zero-arg handler we now discard the
SpeechRecognitionErrorEvent, sonot-allowed,audio-capture, etc. all fail silently; neither developers nor users get any hint about what went wrong. Please keep the event parameter and surface/log the error (or route it into UI state) so we can react appropriately.- recognition.onerror = () => { + recognition.onerror = (event: SpeechRecognitionErrorEvent) => { setIsRecognizing(false); onSpeakingChangeRef.current(false); + console.error('Speech recognition error:', event.error); };backend/websocket/matchmaking.go (1)
119-125: Close the upgraded socket on pool join failure.Once the HTTP connection is upgraded, calling
c.Stringwon’t emit a response; Gin’s writer has already been hijacked. IfAddToPoolever returns an error, the client socket stays open, the connection is still tracked inmatchmakingRoom.clients, and nothing tells the browser the join failed. Please make sure you closeconn, remove the client frommatchmakingRoom.clients, and send an explicit error frame before returning.frontend/src/components/Matchmaking.tsx (1)
137-169: Surface a clear retry path when the socket isn’t ready.The alert-only flow makes the UX brittle—after a transient CLOSE/CONNECTING state, the user can’t recover because no retry is attempted and
wsRefstill points at a defunct socket. Consider queueing thejoin_poolmessage untilOPEN, or at least prompting the reconnection logic described above before throwing the alert.backend/services/matchmaking.go (1)
211-223: Broadcast pool updates after successful matches.After a successful room insert we call the callback, but we never push a new
pool_update. Observers keep seeing the matched players until their sockets close and thedeferinreadPumpfires. Consider callingsendPoolStatus()here so the pool reflects the match immediately.backend/cmd/test_judge/main.go (1)
9-12: Consider making the config path configurable.The config path is hardcoded to
./config/config.prod.yml. For a test harness, consider accepting the path as a command-line argument or environment variable to improve flexibility across different environments.func main() { - cfg, err := config.LoadConfig("./config/config.prod.yml") + configPath := os.Getenv("CONFIG_PATH") + if configPath == "" { + configPath = "./config/config.prod.yml" + } + cfg, err := config.LoadConfig(configPath) if err != nil { panic("failed to load config: " + err.Error()) }backend/controllers/debatevsbot_controller.go (1)
250-251: Simplify complex boolean expression.The boolean logic on these lines is difficult to read and maintain. Consider extracting the compound conditions into named variables or helper functions.
- if strings.Contains(resultLower, "user win") || strings.Contains(resultLower, "user wins") || - strings.Contains(resultLower, "user") && strings.Contains(resultLower, "win") { + hasUserWin := strings.Contains(resultLower, "user win") || strings.Contains(resultLower, "user wins") + hasUserAndWin := strings.Contains(resultLower, "user") && strings.Contains(resultLower, "win") + if hasUserWin || hasUserAndWin { resultStatus = "win"frontend/src/hooks/useDebateWS.ts (1)
264-267: Wrap thepresencecase in a block
Line 265 declaresconst countdirectly under thecase. Lint (noSwitchDeclarations) flagged this because lexical declarations leak across cases. Adding braces isolates the scope and quiets the warning.- case 'presence': - const count = eventData.payload.connected || 0; - setPresence(count); - break; + case 'presence': { + const count = eventData.payload.connected || 0; + setPresence(count); + break; + }Based on static analysis hints
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
backend/go.sumis excluded by!**/*.sumfrontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (98)
backend/cmd/server/main.go(6 hunks)backend/cmd/server/ws_debate_handler.go(1 hunks)backend/cmd/test_judge/main.go(1 hunks)backend/config/config.go(1 hunks)backend/controllers/auth.go(9 hunks)backend/controllers/debate_controller.go(0 hunks)backend/controllers/debatevsbot_controller.go(1 hunks)backend/controllers/leaderboard.go(0 hunks)backend/controllers/profile_controller.go(3 hunks)backend/controllers/team_controller.go(1 hunks)backend/controllers/team_debate_controller.go(1 hunks)backend/controllers/team_matchmaking.go(1 hunks)backend/controllers/transcript_controller.go(8 hunks)backend/db/db.go(2 hunks)backend/go.mod(2 hunks)backend/internal/debate/events.go(1 hunks)backend/internal/debate/poll_store.go(1 hunks)backend/internal/debate/rate_limiter.go(1 hunks)backend/internal/debate/redis_client.go(1 hunks)backend/internal/debate/stream_consumer.go(1 hunks)backend/middlewares/auth.go(2 hunks)backend/models/coach.go(1 hunks)backend/models/debate.go(2 hunks)backend/models/team.go(1 hunks)backend/models/transcript.go(1 hunks)backend/models/user.go(2 hunks)backend/rating/glicko2.go(5 hunks)backend/routes/debate.go(4 hunks)backend/routes/leaderboard.go(1 hunks)backend/routes/pros_cons_route copy.go(1 hunks)backend/routes/rooms.go(9 hunks)backend/routes/team.go(1 hunks)backend/routes/transcriptroutes.go(1 hunks)backend/services/ai.go(0 hunks)backend/services/coach.go(2 hunks)backend/services/debatevsbot.go(5 hunks)backend/services/gemini.go(1 hunks)backend/services/matchmaking.go(6 hunks)backend/services/personalities.go(29 hunks)backend/services/pros_cons.go(2 hunks)backend/services/rating_service.go(4 hunks)backend/services/team_matchmaking.go(1 hunks)backend/services/team_turn_service.go(1 hunks)backend/services/transcriptservice.go(13 hunks)backend/test_server.go(1 hunks)backend/utils/auth.go(5 hunks)backend/utils/debate.go(1 hunks)backend/utils/populate.go(3 hunks)backend/utils/user.go(1 hunks)backend/websocket/handler.go(0 hunks)backend/websocket/matchmaking.go(6 hunks)backend/websocket/team_debate_handler.go(1 hunks)backend/websocket/team_websocket.go(1 hunks)backend/websocket/websocket.go(11 hunks)frontend/package.json(1 hunks)frontend/src/App.tsx(4 hunks)frontend/src/Pages/About.tsx(5 hunks)frontend/src/Pages/Authentication/forms.tsx(0 hunks)frontend/src/Pages/BotSelection.tsx(0 hunks)frontend/src/Pages/DebateRoom.tsx(1 hunks)frontend/src/Pages/Game.tsx(6 hunks)frontend/src/Pages/MatchLogs.tsx(5 hunks)frontend/src/Pages/OnlineDebateRoom.tsx(34 hunks)frontend/src/Pages/Profile.tsx(0 hunks)frontend/src/Pages/SpeechTest.tsx(0 hunks)frontend/src/Pages/StartDebate.tsx(2 hunks)frontend/src/Pages/StrengthenArgument.tsx(1 hunks)frontend/src/Pages/TeamBuilder.tsx(1 hunks)frontend/src/Pages/TeamDebateRoom.tsx(1 hunks)frontend/src/Pages/TournamentHub.tsx(9 hunks)frontend/src/Pages/ViewDebate.tsx(1 hunks)frontend/src/atoms/debateAtoms.ts(1 hunks)frontend/src/components/AnonymousQA.tsx(1 hunks)frontend/src/components/ChatRoom.tsx(6 hunks)frontend/src/components/Chatbox.tsx(1 hunks)frontend/src/components/DebatePopup.tsx(0 hunks)frontend/src/components/JudgementPopup.tsx(9 hunks)frontend/src/components/Layout.tsx(1 hunks)frontend/src/components/Matchmaking.tsx(5 hunks)frontend/src/components/MatchmakingPool.tsx(1 hunks)frontend/src/components/PlayerCard.tsx(1 hunks)frontend/src/components/ReactionBar.tsx(1 hunks)frontend/src/components/RoomBrowser.tsx(1 hunks)frontend/src/components/SavedTranscripts.tsx(0 hunks)frontend/src/components/Sidebar.tsx(1 hunks)frontend/src/components/TeamChatSidebar.tsx(1 hunks)frontend/src/components/TeamMatchmaking.tsx(1 hunks)frontend/src/components/UserCamera.tsx(0 hunks)frontend/src/context/authContext.tsx(0 hunks)frontend/src/context/theme-provider.tsx(1 hunks)frontend/src/hooks/useDebateWS.ts(1 hunks)frontend/src/hooks/useUser.ts(1 hunks)frontend/src/services/teamDebateService.ts(1 hunks)frontend/src/services/teamService.ts(1 hunks)frontend/src/services/transcriptService.ts(0 hunks)frontend/src/services/vsbot.ts(1 hunks)frontend/src/utils/speechTest.ts(1 hunks)frontend/tsconfig.app.tsbuildinfo(1 hunks)
💤 Files with no reviewable changes (13)
- backend/services/ai.go
- frontend/src/components/DebatePopup.tsx
- frontend/src/components/UserCamera.tsx
- frontend/src/Pages/SpeechTest.tsx
- frontend/src/services/transcriptService.ts
- frontend/src/components/SavedTranscripts.tsx
- frontend/src/Pages/BotSelection.tsx
- frontend/src/Pages/Authentication/forms.tsx
- backend/controllers/leaderboard.go
- frontend/src/context/authContext.tsx
- backend/websocket/handler.go
- frontend/src/Pages/Profile.tsx
- backend/controllers/debate_controller.go
🧰 Additional context used
🧬 Code graph analysis (48)
frontend/src/components/ChatRoom.tsx (2)
backend/websocket/websocket.go (1)
Message(66-89)backend/structs/websocket.go (1)
Message(10-13)
backend/services/coach.go (1)
backend/models/coach.go (2)
WeakStatement(4-9)Evaluation(18-21)
backend/controllers/debatevsbot_controller.go (1)
backend/services/transcriptservice.go (1)
SaveDebateTranscript(602-666)
backend/websocket/matchmaking.go (2)
backend/services/matchmaking.go (1)
GetMatchmakingService(39-48)backend/db/db.go (1)
MongoDatabase(16-16)
backend/cmd/test_judge/main.go (3)
backend/config/config.go (1)
LoadConfig(58-70)backend/services/debatevsbot.go (1)
InitDebateVsBotService(21-27)backend/services/transcriptservice.go (1)
JudgeDebateHumanVsHuman(336-426)
frontend/src/components/TeamChatSidebar.tsx (1)
frontend/src/components/Chatbox.tsx (1)
ChatMessage(5-11)
frontend/src/Pages/TeamBuilder.tsx (2)
backend/models/team.go (2)
TeamMember(25-32)Team(11-22)frontend/src/services/teamService.ts (13)
TeamMember(17-24)Team(4-15)getAvailableTeams(142-155)getUserTeams(126-139)createTeam(61-77)joinTeam(96-108)getTeamMemberProfile(308-321)removeMember(276-289)deleteTeam(292-305)updateTeamName(324-339)updateTeamSize(358-373)leaveTeam(111-123)getTeamByCode(342-355)
backend/routes/team.go (4)
backend/controllers/team_controller.go (12)
CreateTeam(175-246)GetTeam(249-270)JoinTeam(273-355)LeaveTeam(358-416)DeleteTeam(521-559)RemoveMember(447-518)UpdateTeamName(49-103)UpdateTeamSize(106-172)GetTeamByCode(30-46)GetTeamMemberProfile(562-588)GetUserTeams(419-444)GetAvailableTeams(591-609)backend/controllers/team_debate_controller.go (3)
CreateTeamDebate(18-94)GetTeamDebate(97-114)GetActiveTeamDebate(117-146)backend/controllers/team_matchmaking.go (4)
JoinMatchmaking(17-69)LeaveMatchmaking(72-82)GetMatchmakingStatus(109-129)GetMatchmakingPool(85-106)backend/services/team_matchmaking.go (1)
GetMatchmakingPool(111-119)
backend/middlewares/auth.go (2)
backend/models/user.go (1)
User(10-30)backend/db/db.go (1)
MongoDatabase(16-16)
frontend/src/hooks/useDebateWS.ts (2)
backend/internal/debate/events.go (1)
Event(9-13)frontend/src/atoms/debateAtoms.ts (9)
wsAtom(4-4)debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)spectatorHashAtom(33-46)PollInfo(9-15)
backend/rating/glicko2.go (1)
backend/config/config.go (1)
Config(10-55)
backend/services/rating_service.go (2)
backend/models/debate.go (1)
Debate(10-25)backend/rating/glicko2.go (1)
Player(21-26)
frontend/src/components/MatchmakingPool.tsx (2)
backend/services/matchmaking.go (1)
MatchmakingPool(16-25)frontend/src/services/teamDebateService.ts (1)
getMatchmakingPool(117-130)
frontend/src/App.tsx (2)
backend/websocket/team_debate_handler.go (1)
TeamDebateRoom(31-35)frontend/src/Pages/ViewDebate.tsx (1)
ViewDebate(18-725)
backend/services/team_matchmaking.go (3)
backend/models/team.go (1)
Team(11-22)backend/db/db.go (1)
GetCollection(20-22)backend/controllers/team_matchmaking.go (1)
GetMatchmakingPool(85-106)
backend/controllers/auth.go (3)
backend/models/user.go (1)
User(10-30)frontend/src/types/user.ts (1)
User(1-21)backend/db/db.go (1)
MongoDatabase(16-16)
frontend/src/Pages/TeamDebateRoom.tsx (4)
frontend/src/services/teamService.ts (2)
TeamMember(17-24)getTeamDebate(179-192)frontend/src/hooks/useUser.ts (1)
useUser(7-59)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/services/teamDebateService.ts (1)
getTeamDebate(55-68)
backend/internal/debate/rate_limiter.go (2)
backend/internal/debate/redis_client.go (2)
GetRedisClient(35-37)GetContext(40-42)backend/internal/debate/poll_store.go (1)
NewPollStore(27-32)
backend/controllers/team_debate_controller.go (3)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (2)
Team(11-22)TeamDebate(61-81)backend/services/team_matchmaking.go (1)
RemoveFromMatchmaking(101-108)
frontend/src/components/JudgementPopup.tsx (1)
frontend/src/components/ui/button.tsx (1)
Button(57-57)
backend/internal/debate/stream_consumer.go (2)
backend/internal/debate/events.go (3)
Event(9-13)UnmarshalEvent(104-110)MarshalEvent(95-101)backend/internal/debate/redis_client.go (2)
GetRedisClient(35-37)GetContext(40-42)
backend/controllers/team_matchmaking.go (3)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (1)
Team(11-22)backend/services/team_matchmaking.go (4)
StartTeamMatchmaking(30-61)RemoveFromMatchmaking(101-108)GetMatchmakingPool(111-119)FindMatchingTeam(64-98)
backend/controllers/team_controller.go (4)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (2)
Team(11-22)TeamMember(25-32)frontend/src/services/teamService.ts (2)
Team(4-15)TeamMember(17-24)backend/models/user.go (1)
User(10-30)
frontend/src/Pages/ViewDebate.tsx (3)
frontend/src/atoms/debateAtoms.ts (6)
debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
backend/websocket/team_debate_handler.go (2)
backend/models/team.go (2)
TeamDebateMessage(84-95)TeamDebate(61-81)backend/db/db.go (1)
GetCollection(20-22)
frontend/src/components/TeamMatchmaking.tsx (3)
backend/models/team.go (1)
Team(11-22)frontend/src/services/teamService.ts (2)
Team(4-15)createTeamDebate(158-176)frontend/src/services/teamDebateService.ts (5)
getActiveTeamDebate(71-84)getMatchmakingStatus(132-145)joinMatchmaking(87-100)leaveMatchmaking(102-115)createTeamDebate(31-52)
frontend/src/services/teamDebateService.ts (3)
frontend/src/utils/auth.ts (1)
getAuthToken(5-7)backend/models/team.go (1)
TeamDebate(61-81)frontend/src/services/teamService.ts (3)
TeamDebate(26-40)createTeamDebate(158-176)getTeamDebate(179-192)
backend/websocket/team_websocket.go (5)
backend/services/team_turn_service.go (5)
TeamTurnManager(132-136)TokenBucket(22-28)TokenBucketService(16-19)NewTeamTurnManager(139-144)NewTokenBucketService(31-35)backend/websocket/websocket.go (1)
Room(29-32)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)backend/db/db.go (1)
MongoDatabase(16-16)backend/models/team.go (2)
TeamDebate(61-81)Team(11-22)
backend/services/transcriptservice.go (5)
backend/controllers/transcript_controller.go (1)
SubmitTranscripts(42-79)backend/db/db.go (1)
MongoDatabase(16-16)backend/models/user.go (1)
User(10-30)backend/models/transcript.go (1)
SavedDebateTranscript(25-37)backend/services/rating_service.go (1)
UpdateRatings(28-106)
backend/routes/pros_cons_route copy.go (2)
backend/services/pros_cons.go (2)
GenerateDebateTopic(14-42)EvaluateProsCons(59-120)backend/services/coach.go (1)
UpdateUserPoints(117-124)
frontend/src/components/AnonymousQA.tsx (2)
frontend/src/atoms/debateAtoms.ts (4)
debateIdAtom(7-7)spectatorHashAtom(33-46)transcriptAtom(49-49)questionsAtom(52-52)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)
backend/cmd/server/main.go (6)
backend/db/db.go (1)
ConnectMongoDB(37-58)backend/internal/debate/redis_client.go (1)
InitRedis(16-32)backend/websocket/websocket.go (1)
WebsocketHandler(227-446)backend/routes/team.go (4)
SetupTeamRoutes(10-27)SetupTeamDebateRoutes(30-37)SetupTeamChatRoutes(51-53)SetupTeamMatchmakingRoutes(40-48)backend/websocket/team_websocket.go (1)
TeamWebsocketHandler(101-380)backend/cmd/server/ws_debate_handler.go (1)
DebateWebsocketHandler(196-264)
backend/test_server.go (1)
backend/services/matchmaking.go (1)
GetMatchmakingService(39-48)
frontend/src/components/ReactionBar.tsx (2)
frontend/src/atoms/debateAtoms.ts (2)
debateIdAtom(7-7)spectatorHashAtom(33-46)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)
backend/services/debatevsbot.go (1)
backend/db/db.go (1)
DebateVsBotCollection(17-17)
backend/routes/debate.go (2)
backend/services/rating_service.go (1)
UpdateRatings(28-106)backend/db/db.go (1)
MongoDatabase(16-16)
frontend/src/services/teamService.ts (3)
backend/models/team.go (3)
Team(11-22)TeamMember(25-32)TeamDebate(61-81)frontend/src/services/teamDebateService.ts (3)
TeamDebate(8-28)createTeamDebate(31-52)getTeamDebate(55-68)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
backend/cmd/server/ws_debate_handler.go (4)
backend/internal/debate/stream_consumer.go (4)
StreamConsumer(18-24)DebateHub(13-15)NewStreamConsumer(27-45)PublishEvent(169-199)backend/internal/debate/events.go (8)
Event(9-13)ClientMessage(75-78)VotePayload(16-22)NewEvent(81-92)QuestionPayload(42-47)ReactionPayload(50-54)CreatePollPayload(25-31)PollCreatedPayload(34-39)backend/internal/debate/rate_limiter.go (2)
NewRateLimiter(18-23)DefaultRateLimitConfig(35-43)backend/internal/debate/poll_store.go (1)
NewPollStore(27-32)
frontend/src/components/Matchmaking.tsx (2)
backend/websocket/matchmaking.go (1)
MatchmakingMessage(48-56)backend/services/matchmaking.go (1)
MatchmakingPool(16-25)
frontend/src/Pages/OnlineDebateRoom.tsx (3)
frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)frontend/src/atoms/debateAtoms.ts (4)
questionsAtom(52-52)presenceAtom(64-64)pollStateAtom(18-18)PollInfo(9-15)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
backend/services/matchmaking.go (1)
backend/db/db.go (1)
MongoDatabase(16-16)
backend/internal/debate/poll_store.go (1)
backend/internal/debate/redis_client.go (2)
GetRedisClient(35-37)GetContext(40-42)
backend/services/pros_cons.go (1)
backend/models/debate.go (1)
ProsConsEvaluation(33-37)
backend/websocket/websocket.go (3)
backend/routes/rooms.go (1)
Room(21-26)backend/structs/websocket.go (2)
Room(30-37)Message(10-13)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)
backend/controllers/profile_controller.go (1)
backend/db/db.go (1)
MongoDatabase(16-16)
backend/services/team_turn_service.go (2)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (1)
Team(11-22)
backend/controllers/transcript_controller.go (2)
backend/services/transcriptservice.go (1)
SubmitTranscripts(19-252)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)
backend/models/team.go (4)
frontend/src/services/teamService.ts (3)
TeamMember(17-24)Team(4-15)TeamDebate(26-40)backend/websocket/websocket.go (1)
Message(66-89)frontend/src/services/teamDebateService.ts (1)
TeamDebate(8-28)backend/websocket/team_debate_handler.go (1)
TeamDebateMessage(37-46)
🪛 Biome (2.1.2)
frontend/src/hooks/useDebateWS.ts
[error] 265-265: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
frontend/src/Pages/TeamDebateRoom.tsx
[error] 443-443: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 444-445: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 446-446: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 512-512: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 524-524: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 525-525: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 526-526: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 552-552: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 553-553: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 567-567: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 568-570: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 571-573: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 574-574: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (22)
frontend/src/context/theme-provider.tsx (1)
26-69: Minor cleanup: console.log removal and formatting — unrelated to PR objectives.Per the AI summary, three debug console.log statements have been removed from
getInitialThemeand its conditional branches, and a trailing newline has been added at the file's end. These are cosmetic/hygiene improvements but orthogonal to the PR's core goals (audience interaction, live polls, anonymous Q&A). The theme provider logic remains unchanged and correct.backend/utils/user.go (1)
14-28: Function logic is sound.The
GetUserIDFromEmailfunction correctly handles context timeouts, email normalization, and error differentiation. Proper error handling distinguishes between "document not found" and other errors.frontend/src/utils/speechTest.ts (2)
56-57: Approved: Clean simplification of error handler.The replacement of a complex typing workaround with a simple no-op
onerrorhandler is a good simplification. For a test utility intentionally suppressing error logging (per PR objectives), this approach is appropriate and maintainable.
21-58: Note: Empty event handlers are intentional per PR logging cleanup.The empty event handlers (
onstart,onresult,onend) align with the PR's objective to remove runtime logging noise. Verify that this test utility is not expected to provide any result callbacks to consumers—if downstream code relies on transcript data, consider adding a callback mechanism or results getter.frontend/src/components/Layout.tsx (1)
2-4: LGTM! Stylistic consistency improvements.The reformatting from double quotes to single quotes in imports and className attributes maintains consistency with the rest of the codebase. No functional changes.
Also applies to: 8-12
frontend/src/Pages/StartDebate.tsx (1)
1-1: Good modernization for React 17+ JSX runtime.Removing the default React import aligns with the automatic JSX transform introduced in React 17+, where React no longer needs to be in scope for JSX to work.
frontend/src/Pages/About.tsx (1)
11-121: Formatting changes look fine, though unrelated to PR objectives.These changes are purely cosmetic—text reflow, adjusted line breaks, and added semicolons—with no impact on functionality or behavior. The formatting is clean and the content remains clear.
However, the About page text updates appear unrelated to this PR's core objectives (audience interaction features, live polls, Q&A). Consider keeping formatting-only changes in separate commits or PRs to maintain clearer change history.
frontend/src/components/PlayerCard.tsx (1)
19-54: Good cleanup of debug logging.Removing the console.log statements for turn duration and isTurn is appropriate for production code and aligns with the broader effort to clean up runtime output across the codebase.
backend/utils/auth.go (1)
30-35: The JWT secret initialization is properly configured at startup.Verified:
SetJWTSecret()is called inbackend/cmd/server/main.go:50during application initialization before request handling begins. The change fromlog.Fataltopanicis safe and more idiomatic, allowing deferred functions to execute for cleanup.backend/routes/pros_cons_route copy.go (2)
24-49: Good defensive validation and error handling.The request struct uses binding constraints (
max=5) that align with the service-layer validation inEvaluateProsCons, which is good defense-in-depth. Error handling is consistent and informative across both branches (JSON binding, evaluation, and points update).
42-46: Verify user context is always populated.Line 42 extracts
user_idviac.GetString("user_id")without validation. This assumes authentication middleware has set this value. Confirm that your auth middleware guarantees this is always present, or add a guard:userID := c.GetString("user_id") if userID == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return }backend/rating/glicko2.go (1)
98-99: LGTM! Timestamp updates correctly placed.The timestamp updates ensure that the next match will properly calculate time decay. The placement is correct—after all rating calculations are complete and after
updateTimeRDhas used the old timestamps.frontend/src/Pages/TournamentHub.tsx (2)
1-43: LGTM! Formatting changes align with style consistency.The import formatting and conversion of string literals to double quotes improve consistency across the codebase. No functional changes detected.
45-98: LGTM! Handler logic remains intact.Formatting adjustments to state management and event handlers maintain functional equivalence with improved readability.
frontend/src/components/Sidebar.tsx (1)
41-45: LGTM!The new "Team Debates" navigation item is cleanly integrated, uses the already-imported
Usersicon, and follows the existingNavItempattern consistently. The placement between Tournaments and Leaderboard is logical.backend/go.mod (1)
3-9: Confirm toolchain availability in CI/CD.Setting
go 1.24plustoolchain go1.24.4will force every build (including CI runners and developer laptops) to download that exact toolchain. Please double-check that your build agents already support the Go toolchain directive and that the named release exists; otherwise builds will start failing with “unsupported go version”. If your infra isn’t ready yet, consider landing this change in a dedicated upgrade PR with the necessary CI updates.backend/services/personalities.go (1)
1-1028: LGTM! Formatting improvements enhance readability.The changes consist solely of field alignment and whitespace adjustments to improve code readability. No functional logic has been altered.
backend/config/config.go (1)
50-54: LGTM! Redis configuration follows existing patterns.The Redis configuration block is properly structured with YAML tags and follows the existing configuration model conventions.
frontend/src/services/vsbot.ts (1)
1-121: LGTM! Debug logging cleanup.The removal of console.log statements and addition of a trailing newline improves code cleanliness without affecting functionality.
frontend/src/Pages/MatchLogs.tsx (1)
121-244: Formatting improvements look good.The line breaks and className adjustments improve code readability without affecting functionality.
frontend/src/components/RoomBrowser.tsx (1)
84-85: Route verification confirmed.The
/view-debate/:debateIDroute is properly configured infrontend/src/App.tsx(line 92) and correctly maps to theViewDebatecomponent. The navigation change inRoomBrowser.tsx(line 85) aligns with this routing setup.frontend/package.json (1)
26-45: No security vulnerabilities detected in new dependencies.The npm audit confirms "found 0 vulnerabilities" across all six newly added packages. Version checks show most dependencies are at their latest releases (@types/react-dnd is intentionally pinned to v2, likely for compatibility). No further action required—the dependencies are safe to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
♻️ Duplicate comments (5)
backend/websocket/websocket.go (1)
166-172: PII leak: Remove email from participant broadcasts.The
broadcastParticipants, exposing participant emails to spectators. This was flagged in a previous review but remains unfixed.Apply this diff to remove the email field:
participants = append(participants, map[string]interface{}{ "id": client.UserID, "displayName": client.Username, - "email": client.Email, "role": client.Role, "isMuted": client.IsMuted, })frontend/src/components/TeamMatchmaking.tsx (1)
72-98: Check the actualTeamDebatepayload instead of fake flags.
getActiveTeamDebatereturns aTeamDebateobject (id,status, …); it never setshasActiveDebateordebateId, so this branch never sees a live debate and the notification never fires.Apply this diff:
- const result: any = await getActiveTeamDebate(team.id); - if (result.hasActiveDebate && result.debateId) { - if (activeDebateId !== result.debateId) { - setActiveDebateId(result.debateId); + const result = await getActiveTeamDebate(team.id); + if (result && result.id && result.status === "active") { + if (activeDebateId !== result.id) { + setActiveDebateId(result.id); setShowDebateNotification(true); } } else {frontend/src/Pages/TeamDebateRoom.tsx (3)
256-259: Stop the permanent loading spinner.When there’s no
debateIdor auth token we still bail out early, butisLoadingnever flips back tofalse, so the UI sits on “Loading debate…” forever. Clear the flag before returning.Apply this diff:
if (!debateId || (!token && !currentUser?.id)) { + setIsLoading(false); return; }
883-905: Stop posting transcripts to localhost.The transcript submission still hits
http://localhost:1313/..., so once deployed the judgment call never reaches the backend. Build the base URL fromVITE_API_URL(fallback: current origin) and reuse it here.Apply this diff:
- const response = await fetch(`http://localhost:1313/submit-transcripts`, { + const rawBaseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; + const baseUrl = rawBaseUrl.replace(/\/+$/, ""); + const response = await fetch(`${baseUrl}/submit-transcripts`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, },
385-405: Use the configured API host for real-time sockets.This WebSocket is hard-wired to
ws://localhost:1313, so production clients disconnect immediately. Derive the host fromVITE_API_URL(or fall back to the current origin) and encode the token before connecting.Apply this diff:
- const ws = new WebSocket( - `ws://localhost:1313/ws/team?debateId=${debateId}&token=${token}` - ); + const apiUrl = import.meta.env.VITE_API_URL; + const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + let host = window.location.host; + if (apiUrl) { + try { + host = new URL(apiUrl).host; + } catch { + host = apiUrl.replace(/^https?:\/\//, ""); + } + } + const ws = new WebSocket( + `${wsProtocol}//${host}/ws/team?debateId=${debateId}&token=${encodeURIComponent( + token + )}` + );
🧹 Nitpick comments (2)
backend/websocket/team_websocket.go (2)
113-113: Consider making the config path configurable.The config path is hardcoded to
"./config/config.prod.yml". For better flexibility across environments (dev, staging, prod), consider passing this as a parameter or loading from an environment variable.
604-756: Consider breaking down this complex function.
handleTeamReadyStatusis 150+ lines and handles multiple concerns: ready status assignment, counting, broadcasting, and countdown logic. While the implementation is correct (mutex is held during all critical sections), splitting into smaller helper functions would improve maintainability:
assignReadyStatuscountTeamReadinessbroadcastReadyUpdatemaybeStartCountdown
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
backend/services/team_turn_service.go(1 hunks)backend/websocket/team_websocket.go(1 hunks)backend/websocket/websocket.go(11 hunks)frontend/src/Pages/OnlineDebateRoom.tsx(35 hunks)frontend/src/Pages/TeamBuilder.tsx(1 hunks)frontend/src/Pages/TeamDebateRoom.tsx(1 hunks)frontend/src/Pages/ViewDebate.tsx(1 hunks)frontend/src/components/ReactionBar.tsx(1 hunks)frontend/src/components/TeamMatchmaking.tsx(1 hunks)frontend/src/hooks/useDebateWS.ts(1 hunks)frontend/src/services/teamDebateService.ts(1 hunks)frontend/src/services/teamService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/services/teamService.ts
🧰 Additional context used
🧬 Code graph analysis (11)
backend/websocket/team_websocket.go (5)
backend/services/team_turn_service.go (5)
TeamTurnManager(132-136)TokenBucket(22-28)TokenBucketService(16-19)NewTeamTurnManager(139-144)NewTokenBucketService(31-35)backend/websocket/websocket.go (1)
Room(29-32)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)backend/db/db.go (1)
MongoDatabase(16-16)backend/models/team.go (2)
TeamDebate(61-81)Team(11-22)
frontend/src/hooks/useDebateWS.ts (2)
backend/internal/debate/events.go (1)
Event(9-13)frontend/src/atoms/debateAtoms.ts (9)
wsAtom(4-4)debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)spectatorHashAtom(33-46)PollInfo(9-15)
backend/websocket/websocket.go (2)
backend/structs/websocket.go (2)
Room(30-37)Message(10-13)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)
backend/services/team_turn_service.go (2)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (1)
Team(11-22)
frontend/src/Pages/TeamBuilder.tsx (3)
frontend/src/services/teamService.ts (13)
TeamMember(18-25)Team(5-16)getAvailableTeams(143-156)getUserTeams(127-140)createTeam(62-78)joinTeam(97-109)getTeamMemberProfile(309-322)removeMember(277-290)deleteTeam(293-306)updateTeamName(325-340)updateTeamSize(359-374)leaveTeam(112-124)getTeamByCode(343-356)backend/models/team.go (2)
TeamMember(25-32)Team(11-22)frontend/src/state/userAtom.ts (1)
userAtom(4-4)
frontend/src/components/ReactionBar.tsx (2)
frontend/src/atoms/debateAtoms.ts (2)
debateIdAtom(7-7)spectatorHashAtom(33-46)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)
frontend/src/components/TeamMatchmaking.tsx (4)
frontend/src/services/teamService.ts (2)
Team(5-16)createTeamDebate(159-177)backend/models/team.go (1)
Team(11-22)backend/models/user.go (1)
User(10-30)frontend/src/services/teamDebateService.ts (5)
getActiveTeamDebate(95-108)getMatchmakingStatus(156-171)joinMatchmaking(111-124)leaveMatchmaking(126-139)createTeamDebate(55-76)
frontend/src/Pages/ViewDebate.tsx (5)
frontend/src/atoms/debateAtoms.ts (6)
debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/components/AnonymousQA.tsx (1)
AnonymousQA(9-161)frontend/src/components/ReactionBar.tsx (1)
ReactionBar(52-88)
frontend/src/Pages/TeamDebateRoom.tsx (4)
frontend/src/services/teamService.ts (2)
TeamMember(18-25)getTeamDebate(180-193)frontend/src/hooks/useUser.ts (1)
useUser(7-59)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/services/teamDebateService.ts (1)
getTeamDebate(79-92)
frontend/src/services/teamDebateService.ts (3)
frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/services/teamService.ts (5)
TeamDebate(27-41)TeamMember(18-25)Team(5-16)createTeamDebate(159-177)getTeamDebate(180-193)backend/models/team.go (3)
TeamDebate(61-81)TeamMember(25-32)Team(11-22)
frontend/src/Pages/OnlineDebateRoom.tsx (3)
frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-305)frontend/src/atoms/debateAtoms.ts (4)
questionsAtom(52-52)presenceAtom(64-64)pollStateAtom(18-18)PollInfo(9-15)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
🪛 Biome (2.1.2)
frontend/src/hooks/useDebateWS.ts
[error] 265-265: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
frontend/src/Pages/TeamDebateRoom.tsx
[error] 481-481: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 482-483: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 484-484: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 550-550: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 581-581: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 582-582: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 596-596: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 597-599: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 600-602: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 603-603: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (16)
frontend/src/components/ReactionBar.tsx (3)
1-7: LGTM! Clean imports and reaction set.The imports are appropriate and the REACTIONS array provides a good variety of emojis for user engagement.
14-50: Nice animation implementation with good accessibility.The spring animation provides smooth visual feedback, and the
aria-labelensures the buttons are accessible to screen readers.
73-87: Clean rendering with good positioning and dark mode support.The fixed bottom-center positioning is appropriate for a reaction bar, and the dark mode classes ensure good UX across themes.
backend/services/team_turn_service.go (5)
102-108: Data race fixed—exclusive lock is now correct.The previous critical issue where
GetRemainingTokensheld a read lock while mutating viarefillTokenshas been resolved. The function now correctly acquiresbucket.Mutex.Lock()(exclusive lock) before refilling, preventing concurrent access issues.
64-88: LGTM—concurrency handling is correct.The two-level locking (map-level RLock at line 68, then bucket-level Lock at line 76) is appropriate. Token refill and consumption are properly protected by the exclusive bucket lock.
185-220: LGTM—turn advancement logic is sound.The circular turn advancement (line 215) is correct, and error handling for missing team/turn state returns
primitive.NilObjectIDas expected. Linear search throughturnOrderis acceptable for typical team sizes.
111-124: LGTM—refill logic is correct.The time-based token refill correctly caps at
Capacityand only updatesLastRefillwhen tokens are added. Note that sub-second elapsed time is truncated (line 115), so tokens refill in 1-second increments, which is acceptable for this use case.
15-35: LGTM—two-level locking design is appropriate.The service-level
mutex(line 18) protects thebucketsmap, while eachTokenBuckethas its ownMutex(line 27) for fine-grained concurrency control. This allows parallel operations on different buckets while still protecting the map structure.backend/websocket/team_websocket.go (4)
222-231: Excellent defense-in-depth validation.This redundant check ensures
userTeamIDmatches one of the debate teams even if earlier logic has bugs. Good use of fail-safe validation before creating the client.
723-751: Verify goroutine safety after room deletion.The goroutine sleeps for 3 seconds before acquiring locks. If all clients disconnect during this window, the room will be deleted (lines 322-326), but the goroutine still runs. The check at line 730 (
if !stillExists) handles this, which is good.The direct iteration over
room.Clientsat lines 743-747 is safe becauseroom.Mutexis held during the loop.
401-401: Past review concern addressed.Good fix! All handlers now use
snapshotTeamRecipientsto safely iterate clients instead of directly accessingroom.Clients, addressing the concurrent map iteration issue from the previous review.
758-803: LGTM: Token bucket integration.The turn request logic correctly checks
CanUserSpeakbefore granting permission and updates the client with remaining tokens. Good integration with the token bucket service.backend/websocket/websocket.go (4)
607-626: Past review concern addressed: double-unlock fixed.Good fix! Using
defer room.Mutex.Unlock()at line 610 ensures the mutex is released exactly once on all return paths, resolving the double-unlock panic from the previous review.
282-334: LGTM: Spectator join flow.The spectator connection logic correctly:
- Bypasses the 2-debater limit for spectators
- Assigns unique
ConnectionIDviauuid.New()- Sends participant snapshot to new connection
- Notifies other clients of spectator join
340-370: LGTM: Disconnect handling with spectator support.The disconnect logic properly handles spectators by:
- Detecting spectator disconnections (line 361)
- Notifying debaters via
notifySpectatorStatus(line 363)- Broadcasting updated participant counts (line 368)
116-224: Well-structured spectator helpers.The new helper functions properly use mutex locking and provide clear separation of concerns for spectator vs. debater management. Good design for maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (11)
frontend/src/Pages/OnlineDebateRoom.tsx (2)
319-329: Resolved: ICE candidate payload now uses live userId.The candidate payload reads userId from the ref-sourced local variable at send time. This fixes the stale-closure bug.
560-567: Resolved: judgment polling interval is cleared on unmount.Cleanup added for judgePollRef prevents leaks and setState-on-unmounted warnings.
frontend/src/hooks/useDebateWS.ts (2)
39-49: Resolved: WS host now derived from VITE_API_URL/current origin.Good fallback and protocol handling.
268-272: Resolved: presence case wrapped in a block.Matches linter expectations and prevents scope bleed.
frontend/src/Pages/ViewDebate.tsx (2)
83-96: Resolved: WS host configurable and token encoded.This unblocks deployed environments.
314-339: Resolved: send the resolved connectionId in answer payload.Fix ensures SDP handshakes complete.
frontend/src/Pages/TeamDebateRoom.tsx (4)
257-259: Stop the endless spinner on early return.Set isLoading to false before returning when debateId/auth is missing.
- if (!debateId || (!token && !currentUser?.id)) { - return; - } + if (!debateId || (!token && !currentUser?.id)) { + setIsLoading(false); + return; + }
887-901: Make submit-transcripts base URL configurable.Same localhost issue for HTTP; derive from VITE_API_URL/current origin.
- const response = await fetch(`http://localhost:1313/submit-transcripts`, { + const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; + const response = await fetch(`${baseUrl}/submit-transcripts`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ roomId: debateId, role: localRole, transcripts: debateTranscripts, }), });
390-392: Don’t hard‑code WS host; use env/origin and encode params.Use VITE_API_URL or current origin and URL‑encode debateId/token.
- const ws = new WebSocket( - `ws://localhost:1313/ws/team?debateId=${debateId}&token=${token}` - ); + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const apiUrl = import.meta.env.VITE_API_URL; + let host = window.location.host; + if (apiUrl) { + try { host = new URL(apiUrl).host; } catch { host = apiUrl.replace(/^https?:\/\//, ""); } + } + const ws = new WebSocket( + `${protocol}//${host}/ws/team?debateId=${encodeURIComponent(debateId)}&token=${encodeURIComponent(token)}` + );
642-657: Use the latest team flag inside WS handler to avoid stale reads.Replace isTeam1 with isTeam1Latest in teamStatus handling.
- if (data.team1Members) { - if (isTeam1) { + if (data.team1Members) { + if (isTeam1Latest) { setMyTeamMembers(data.team1Members); } else { setOpponentTeamMembers(data.team1Members); } } - if (data.team2Members) { - if (isTeam1) { + if (data.team2Members) { + if (isTeam1Latest) { setOpponentTeamMembers(data.team2Members); } else { setMyTeamMembers(data.team2Members); } }backend/websocket/websocket.go (1)
166-172: Stop leaking participant emails to spectators. Line 166 still injectsclient.Emailinto the participant payload that you broadcast to every connection. BecausesnapshotRecipientsincludes spectators, anyone joining the room can read every debater’s email—this is the same privacy/compliance defect flagged earlier. Strip the email before broadcasting (or send it only over a non-spectator channel) so PII never leaves authorized recipients.@@ - participants = append(participants, map[string]interface{}{ - "id": client.UserID, - "displayName": client.Username, - "email": client.Email, - "role": client.Role, - "isMuted": client.IsMuted, - }) + participant := map[string]interface{}{ + "id": client.UserID, + "displayName": client.Username, + "role": client.Role, + "isMuted": client.IsMuted, + } + participants = append(participants, participant)
🧹 Nitpick comments (4)
frontend/src/Pages/OnlineDebateRoom.tsx (2)
1320-1330: Handle rejected video.play() promises.Avoid unhandled promise rejections when autoplay is blocked.
- localVideoRef.current.play(); + localVideoRef.current.play().catch(() => {}); @@ - remoteVideoRef.current.play(); + remoteVideoRef.current.play().catch(() => {});
599-603: Verify necessity of sending opponentEmail (PII) to backend.Emails are PII; avoid transmitting unless strictly required server-side. If an ID suffices, drop email from the payload.
Would you like a follow-up patch that removes opponentEmail and uses opponentId only?
Also applies to: 721-723
frontend/src/hooks/useDebateWS.ts (2)
58-59: Encode path/query parts in wsUrl.Protect against special characters in debateId/spectatorId.
- const wsUrl = `${protocol}//${host}/ws/debate/${debateId}${spectatorId ? `?spectatorId=${spectatorId}` : ''}`; + const wsUrl = `${protocol}//${host}/ws/debate/${encodeURIComponent(debateId)}${ + spectatorId ? `?spectatorId=${encodeURIComponent(spectatorId)}` : "" + }`;
289-297: Reset atoms on unmount to avoid stale UI after navigating away.Clear debateId, pollState, questions, reactions, and presence in the cleanup.
return () => { if (rws) { rws.close(); wsRef.current = null; setWs(null); setWsStatus('disconnected'); + setDebateId(null); + setPollState({}); + setQuestions([]); + setReactions([]); + setPresence(0); } };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
backend/services/team_turn_service.go(1 hunks)backend/websocket/team_websocket.go(1 hunks)backend/websocket/websocket.go(11 hunks)frontend/src/Pages/OnlineDebateRoom.tsx(35 hunks)frontend/src/Pages/TeamDebateRoom.tsx(1 hunks)frontend/src/Pages/ViewDebate.tsx(1 hunks)frontend/src/components/ReactionBar.tsx(1 hunks)frontend/src/hooks/useDebateWS.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/ReactionBar.tsx
🧰 Additional context used
🧬 Code graph analysis (7)
frontend/src/hooks/useDebateWS.ts (2)
backend/internal/debate/events.go (1)
Event(9-13)frontend/src/atoms/debateAtoms.ts (9)
wsAtom(4-4)debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)spectatorHashAtom(33-46)PollInfo(9-15)
frontend/src/Pages/ViewDebate.tsx (5)
frontend/src/atoms/debateAtoms.ts (6)
debateIdAtom(7-7)pollStateAtom(18-18)questionsAtom(52-52)reactionsAtom(55-55)wsStatusAtom(58-58)presenceAtom(64-64)frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-310)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/components/AnonymousQA.tsx (1)
AnonymousQA(9-161)frontend/src/components/ReactionBar.tsx (1)
ReactionBar(52-91)
backend/websocket/team_websocket.go (4)
backend/services/team_turn_service.go (5)
TeamTurnManager(155-159)TokenBucket(28-34)TokenBucketService(22-25)NewTeamTurnManager(162-167)NewTokenBucketService(37-41)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)backend/db/db.go (1)
MongoDatabase(16-16)backend/models/team.go (2)
TeamDebate(61-81)Team(11-22)
backend/websocket/websocket.go (2)
backend/structs/websocket.go (2)
Room(30-37)Message(10-13)backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(127-140)
backend/services/team_turn_service.go (2)
backend/db/db.go (1)
GetCollection(20-22)backend/models/team.go (1)
Team(11-22)
frontend/src/Pages/OnlineDebateRoom.tsx (3)
frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(22-310)frontend/src/atoms/debateAtoms.ts (4)
questionsAtom(52-52)presenceAtom(64-64)pollStateAtom(18-18)PollInfo(9-15)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
frontend/src/Pages/TeamDebateRoom.tsx (4)
frontend/src/services/teamService.ts (2)
TeamMember(18-25)getTeamDebate(180-193)frontend/src/hooks/useUser.ts (1)
useUser(7-59)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)frontend/src/services/teamDebateService.ts (1)
getTeamDebate(79-92)
🪛 Biome (2.1.2)
frontend/src/Pages/TeamDebateRoom.tsx
[error] 487-487: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 488-489: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 490-490: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 556-556: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 587-587: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 588-588: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 602-602: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 603-605: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 606-608: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 609-609: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
| turnManager := services.NewTeamTurnManager() | ||
| tokenBucket := services.NewTokenBucketService() | ||
|
|
||
| // Initialize turn management for both teams | ||
| turnManager.InitializeTeamTurns(debate.Team1ID) | ||
| turnManager.InitializeTeamTurns(debate.Team2ID) | ||
|
|
||
| // Initialize token buckets for both teams | ||
| tokenBucket.InitializeTeamBuckets(debate.Team1ID) | ||
| tokenBucket.InitializeTeamBuckets(debate.Team2ID) | ||
|
|
||
| teamRooms[roomKey] = &TeamRoom{ | ||
| Clients: make(map[*websocket.Conn]*TeamClient), | ||
| Team1ID: debate.Team1ID, | ||
| Team2ID: debate.Team2ID, | ||
| DebateID: debateObjectID, | ||
| TurnManager: turnManager, | ||
| TokenBucket: tokenBucket, | ||
| CurrentTopic: debate.Topic, | ||
| CurrentPhase: "setup", | ||
| Team1Role: debate.Team1Stance, | ||
| Team2Role: debate.Team2Stance, | ||
| Team1Ready: make(map[string]bool), | ||
| Team2Ready: make(map[string]bool), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle turn/token initialization failures. Lines 192-209 drop the errors from InitializeTeamTurns and InitializeTeamBuckets. If the DB lookup hiccups or a team has no members, you proceed with empty turn order and missing buckets, so GetCurrentTurn returns Nil and every TryConsumeForSpeaking denies the speaker. Fail fast while you still own the HTTP response instead of entering the WS loop in a broken state.
@@
- turnManager := services.NewTeamTurnManager()
- tokenBucket := services.NewTokenBucketService()
-
- // Initialize turn management for both teams
- turnManager.InitializeTeamTurns(debate.Team1ID)
- turnManager.InitializeTeamTurns(debate.Team2ID)
-
- // Initialize token buckets for both teams
- tokenBucket.InitializeTeamBuckets(debate.Team1ID)
- tokenBucket.InitializeTeamBuckets(debate.Team2ID)
+ turnManager := services.NewTeamTurnManager()
+ if err := turnManager.InitializeTeamTurns(debate.Team1ID); err != nil {
+ teamRoomsMutex.Unlock()
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initialize turn order for team1"})
+ return
+ }
+ if err := turnManager.InitializeTeamTurns(debate.Team2ID); err != nil {
+ teamRoomsMutex.Unlock()
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initialize turn order for team2"})
+ return
+ }
+
+ tokenBucket := services.NewTokenBucketService()
+ if err := tokenBucket.InitializeTeamBuckets(debate.Team1ID); err != nil {
+ teamRoomsMutex.Unlock()
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initialize speaking tokens for team1"})
+ return
+ }
+ if err := tokenBucket.InitializeTeamBuckets(debate.Team2ID); err != nil {
+ teamRoomsMutex.Unlock()
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initialize speaking tokens for team2"})
+ return
+ }🤖 Prompt for AI Agents
In backend/websocket/team_websocket.go around lines 188 to 212, the code
currently ignores errors from InitializeTeamTurns and InitializeTeamBuckets
which can leave the room in a broken state; update those service method calls to
check returned errors (and any returned status indicating empty results), and if
any initialization fails or yields empty/no members, log the detailed error,
write an HTTP error response (e.g. 500 with a clear message) and abort creating
the TeamRoom (do not assign teamRooms[roomKey] or enter the WS loop). Ensure the
Initialize* functions return errors if they don’t already, propagate those
errors here, and only create and store the TeamRoom when all initializations
succeed and validated (non-empty turn order and token buckets present).
| const pollResponse = await fetch( | ||
| `http://localhost:1313/submit-transcripts`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| body: JSON.stringify({ roomId, role, transcripts: {} }), | ||
| } | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make submit-transcripts base URL configurable.
Use VITE_API_URL (or current origin) instead of http://localhost:1313 and avoid plain host leakage.
- const pollResponse = await fetch(
- `http://localhost:1313/submit-transcripts`,
- {
+ const baseUrl =
+ import.meta.env.VITE_API_URL ??
+ `${window.location.protocol}//${window.location.host}`;
+ const pollResponse = await fetch(`${baseUrl}/submit-transcripts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ roomId, role, transcripts: {} }),
- }
- );
+ });- const response = await fetch(`http://localhost:1313/submit-transcripts`, {
+ const baseUrl =
+ import.meta.env.VITE_API_URL ??
+ `${window.location.protocol}//${window.location.host}`;
+ const response = await fetch(`${baseUrl}/submit-transcripts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
roomId,
role,
transcripts,
opponentRole,
opponentId,
opponentEmail,
opponentTranscripts,
}),
});Also applies to: 589-606
🤖 Prompt for AI Agents
In frontend/src/Pages/OnlineDebateRoom.tsx around lines 497-507 (and likewise
update lines 589-606), the request is hardcoded to http://localhost:1313 which
leaks the host; replace the hardcoded URL with a configurable base derived from
import.meta.env.VITE_API_URL fallback to window.location.origin (or an agreed
runtime config), then build the full endpoint by concatenating the base and
"/submit-transcripts" so the fetch uses `${base}/submit-transcripts`; ensure you
handle missing trailing slashes when joining and keep using the same
headers/body logic.
| const wsUrl = `ws://localhost:1313/ws?room=${roomId}&token=${token}`; | ||
| const rws = new ReconnectingWebSocket(wsUrl, [], { | ||
| connectionTimeout: 4000, | ||
| maxRetries: Infinity, | ||
| maxReconnectionDelay: 10000, | ||
| minReconnectionDelay: 1000, | ||
| reconnectionDelayGrowFactor: 1.3, | ||
| }); | ||
| wsRef.current = rws; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop hard-coding WS host; derive from env/origin and URL‑encode the token.
Build the ws URL from VITE_API_URL (fallback to current origin) and encode query params.
- const wsUrl = `ws://localhost:1313/ws?room=${roomId}&token=${token}`;
- const rws = new ReconnectingWebSocket(wsUrl, [], {
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
+ const apiUrl = import.meta.env.VITE_API_URL;
+ let host = window.location.host;
+ if (apiUrl) {
+ try { host = new URL(apiUrl).host; } catch { host = apiUrl.replace(/^https?:\/\//, ""); }
+ }
+ const wsUrl = `${protocol}//${host}/ws?room=${encodeURIComponent(roomId!)}&token=${encodeURIComponent(token)}`;
+ const rws = new ReconnectingWebSocket(wsUrl, [], {
connectionTimeout: 4000,
maxRetries: Infinity,
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.3,
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const wsUrl = `ws://localhost:1313/ws?room=${roomId}&token=${token}`; | |
| const rws = new ReconnectingWebSocket(wsUrl, [], { | |
| connectionTimeout: 4000, | |
| maxRetries: Infinity, | |
| maxReconnectionDelay: 10000, | |
| minReconnectionDelay: 1000, | |
| reconnectionDelayGrowFactor: 1.3, | |
| }); | |
| wsRef.current = rws; | |
| const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; | |
| const apiUrl = import.meta.env.VITE_API_URL; | |
| let host = window.location.host; | |
| if (apiUrl) { | |
| try { host = new URL(apiUrl).host; } catch { host = apiUrl.replace(/^https?:\/\//, ""); } | |
| } | |
| const wsUrl = `${protocol}//${host}/ws?room=${encodeURIComponent(roomId!)}&token=${encodeURIComponent(token)}`; | |
| const rws = new ReconnectingWebSocket(wsUrl, [], { | |
| connectionTimeout: 4000, | |
| maxRetries: Infinity, | |
| maxReconnectionDelay: 10000, | |
| minReconnectionDelay: 1000, | |
| reconnectionDelayGrowFactor: 1.3, | |
| }); | |
| wsRef.current = rws; |
🤖 Prompt for AI Agents
In frontend/src/Pages/OnlineDebateRoom.tsx around lines 1018 to 1026, the
WebSocket URL is hard-coded to ws://localhost:1313 and the token/query params
are not URL-encoded; change it to derive the base API URL from
import.meta.env.VITE_API_URL (falling back to window.location.origin), normalize
its protocol to ws or wss based on the origin (or swap http->ws, https->wss),
then build the socket URL by URL-encoding roomId and token with
encodeURIComponent when constructing the query string so no raw values or
hard-coded host/port remain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/src/Pages/OnlineDebateRoom.tsx (2)
870-880: Replace hard-coded localhost in room creation.The room creation endpoint uses
http://localhost:1313which won't work in deployed environments.Apply this diff:
+ const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; const response = await fetch( - `http://localhost:1313/rooms`, + `${baseUrl}/rooms`, {
902-909: Replace hard-coded localhost in participants fetch.The participants fetch endpoint uses
http://localhost:1313which won't work in deployed environments.Apply this diff:
+ const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; const response = await fetch( - `http://localhost:1313/rooms/${roomId}/participants`, + `${baseUrl}/rooms/${roomId}/participants`, {backend/websocket/team_websocket.go (1)
188-197: Add error handling for initialization calls—currently errors are silently ignored.Both
InitializeTeamTurnsandInitializeTeamBucketsreturn errors, but the code at lines 188–197 discards these returns. This allows the WebSocket handler to proceed with broken state if initialization fails. Check errors after each call and return early if any fail.Example pattern:
if err := turnManager.InitializeTeamTurns(debate.Team1ID); err != nil { // log error and return }
♻️ Duplicate comments (7)
frontend/src/Pages/TeamBuilder.tsx (1)
403-412: Unresolved: Feedback still hidden after joining a team.The error/success alerts remain inside the Create Team card (which disappears when
isUserInTeamis true), so feedback from rename, resize, remove member, copy code, leave, and delete operations won't be visible to users who are already in a team.backend/internal/debate/poll_store.go (1)
121-126: Validate the option before incrementing the vote count.
HIncrBywill create a new hash field if the option doesn't exist, allowing spectators to vote for arbitrary strings. This corrupts poll results. The backend should verify the option exists in the poll's valid options before incrementing.Apply this diff to validate the option:
+ // Verify option exists in poll metadata + metaKey := fmt.Sprintf("debate:%s:poll:%s:meta", debateID, pollID) + metaStr, err := ps.rdb.Get(ps.ctx, metaKey).Result() + if err != nil { + ps.rdb.SRem(ps.ctx, votersKey, spectatorHash) + return false, fmt.Errorf("failed to retrieve poll metadata: %w", err) + } + + var meta PollMetadata + if err := json.Unmarshal([]byte(metaStr), &meta); err != nil { + ps.rdb.SRem(ps.ctx, votersKey, spectatorHash) + return false, fmt.Errorf("failed to parse poll metadata: %w", err) + } + + // Check if option is valid + validOption := false + for _, opt := range meta.Options { + if opt == option { + validOption = true + break + } + } + if !validOption { + ps.rdb.SRem(ps.ctx, votersKey, spectatorHash) + return false, fmt.Errorf("invalid option: %s", option) + } + // Increment poll count atomically if err := ps.rdb.HIncrBy(ps.ctx, countsKey, option, 1).Err(); err != nil { // Rollback voter add ps.rdb.SRem(ps.ctx, votersKey, spectatorHash) return false, fmt.Errorf("failed to increment count: %w", err) }Based on learnings
frontend/src/Pages/TeamDebateRoom.tsx (1)
409-411: Replace hard-coded localhost with environment-aware WebSocket URL.The WebSocket URL is hard-coded to
ws://localhost:1313, which will fail in staging/production environments. Build the URL fromVITE_API_URL(or fallback towindow.location.host) and select protocol based on page protocol.Apply this diff:
- const ws = new WebSocket( - `ws://localhost:1313/ws/team?debateId=${debateId}&token=${token}` - ); + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const apiUrl = import.meta.env.VITE_API_URL; + let host = window.location.host; + if (apiUrl) { + try { + host = new URL(apiUrl).host; + } catch { + host = apiUrl.replace(/^https?:\/\//, ""); + } + } + const ws = new WebSocket( + `${protocol}//${host}/ws/team?debateId=${debateId}&token=${token}` + );frontend/src/Pages/OnlineDebateRoom.tsx (3)
511-520: Replace hard-coded localhost in judgment polling.The polling endpoint uses
http://localhost:1313which won't work in deployed environments.Apply this diff:
+ const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; const pollResponse = await fetch( - `http://localhost:1313/submit-transcripts`, + `${baseUrl}/submit-transcripts`, { method: "POST",
597-615: Replace hard-coded localhost in transcript submission.The transcript submission endpoint uses
http://localhost:1313which won't work in deployed environments.Apply this diff:
+ const baseUrl = + import.meta.env.VITE_API_URL ?? + `${window.location.protocol}//${window.location.host}`; const response = await fetch( - `http://localhost:1313/submit-transcripts`, + `${baseUrl}/submit-transcripts`, { method: "POST",
1087-1094: Replace hard-coded WebSocket URL with environment-aware construction.The WebSocket URL uses
ws://localhost:1313which won't work in deployed environments. Build fromVITE_API_URLor current origin, and URL-encode query parameters.Apply this diff:
- const wsUrl = `ws://localhost:1313/ws?room=${roomId}&token=${token}`; + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const apiUrl = import.meta.env.VITE_API_URL; + let host = window.location.host; + if (apiUrl) { + try { + host = new URL(apiUrl).host; + } catch { + host = apiUrl.replace(/^https?:\/\//, ""); + } + } + const wsUrl = `${protocol}//${host}/ws?room=${encodeURIComponent( + roomId! + )}&token=${encodeURIComponent(token)}`; const rws = new ReconnectingWebSocket(wsUrl, [], {backend/websocket/websocket.go (1)
169-175: Remove email from participant broadcast to prevent PII leakage.The
buildParticipantsMessagefunction includes"email": client.Emailin the participant data that is broadcast to all clients, including spectators. This exposes debater emails to anyone watching the debate, creating a privacy/compliance risk.Apply this diff to remove the email field:
participants = append(participants, map[string]interface{}{ "id": client.UserID, "displayName": client.Username, - "email": client.Email, "role": client.Role, "isMuted": client.IsMuted, })
🧹 Nitpick comments (1)
frontend/src/hooks/useDebateWS.ts (1)
123-192: Consider extracting poll normalization logic to reduce duplication.The poll normalization logic (parsing counts, deduplicating options, trimming strings) is repeated in both
poll_snapshot(lines 128-167) andpoll_created(lines 232-256). This duplication increases maintenance burden.Consider extracting to a helper function:
+const normalizePollData = (poll: any): PollInfo | null => { + const pollId = typeof poll.pollId === 'string' ? poll.pollId : String(poll.pollId ?? ''); + if (!pollId) return null; + + const countsRaw = poll.counts || {}; + const counts: Record<string, number> = {}; + Object.entries(countsRaw).forEach(([option, value]) => { + const numericValue = typeof value === 'number' ? value : Number(value ?? 0) || 0; + counts[option] = numericValue; + }); + + let options: string[] = []; + if (Array.isArray(poll.options)) { + options = poll.options + .map((opt: unknown) => String(opt ?? '').trim()) + .filter((opt: string) => opt.length > 0); + } + if (options.length === 0) { + options = Object.keys(counts); + } + + return { + pollId, + question: typeof poll.question === 'string' ? poll.question : '', + options, + counts, + voters: typeof poll.voters === 'number' ? poll.voters : Number(poll.voters ?? 0) || 0, + }; +}; case 'poll_snapshot': { const payload = eventData.payload || {}; const pollsPayload = payload.polls; if (Array.isArray(pollsPayload)) { const nextState: Record<string, PollInfo> = {}; pollsPayload.forEach((poll) => { - if (!poll) return; - const pollId = typeof poll.pollId === 'string' ? poll.pollId : String(poll.pollId ?? ''); - // ... rest of normalization + const info = normalizePollData(poll); + if (info) nextState[info.pollId] = info; }); setPollState(nextState);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
backend/controllers/transcript_controller.go(2 hunks)backend/internal/debate/poll_store.go(5 hunks)backend/websocket/team_websocket.go(9 hunks)backend/websocket/websocket.go(10 hunks)frontend/src/Pages/OnlineDebateRoom.tsx(10 hunks)frontend/src/Pages/TeamBuilder.tsx(1 hunks)frontend/src/Pages/TeamDebateRoom.tsx(14 hunks)frontend/src/atoms/debateAtoms.ts(2 hunks)frontend/src/components/Matchmaking.tsx(1 hunks)frontend/src/components/ReactionBar.tsx(1 hunks)frontend/src/components/TeamMatchmaking.tsx(0 hunks)frontend/src/hooks/useDebateWS.ts(4 hunks)frontend/src/services/teamDebateService.ts(3 hunks)frontend/src/services/teamService.ts(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/components/TeamMatchmaking.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- frontend/src/services/teamDebateService.ts
- frontend/src/components/ReactionBar.tsx
- backend/controllers/transcript_controller.go
- frontend/src/components/Matchmaking.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/Pages/OnlineDebateRoom.tsx (2)
frontend/src/hooks/useDebateWS.ts (1)
useDebateWS(23-347)frontend/src/atoms/debateAtoms.ts (4)
questionsAtom(63-63)presenceAtom(75-75)pollStateAtom(18-18)PollInfo(9-15)
backend/websocket/team_websocket.go (1)
backend/services/team_turn_service.go (1)
TokenBucket(28-34)
frontend/src/hooks/useDebateWS.ts (1)
frontend/src/atoms/debateAtoms.ts (2)
lastEventIdAtom(72-72)PollInfo(9-15)
backend/websocket/websocket.go (1)
backend/structs/websocket.go (1)
Room(30-37)
🪛 Biome (2.1.2)
frontend/src/Pages/TeamDebateRoom.tsx
[error] 997-997: Shouldn't redeclare 'toggleCamera'. Consider to delete it or rename it.
'toggleCamera' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (24)
frontend/src/Pages/TeamBuilder.tsx (1)
634-645: LGTM! Good defensive check and sanitization.The conditional rendering with
user && user.idprevents potential NPEs, and the sanitized user object with fallback values ensures TeamMatchmaking receives a well-defined payload. This aligns well with the optional chaining pattern used elsewhere in the component.frontend/src/atoms/debateAtoms.ts (1)
9-18: LGTM: Clean poll state refactor with improved type safety.The new
PollInfointerface and refactoredpollStateAtomprovide better structure and type safety for poll data. The fields (pollId, question, options, counts, voters) align well with the backendPollMetadatastructure and support the expanded poll functionality introduced in this PR.frontend/src/hooks/useDebateWS.ts (1)
66-76: LGTM: Proper environment-aware WebSocket URL construction.The WebSocket URL derivation correctly uses
VITE_API_URLwhen available and falls back towindow.location.host, addressing the previous hard-coded localhost issue. The try-catch for URL parsing with fallback to string replacement is a good defensive pattern.backend/internal/debate/poll_store.go (2)
34-99: LGTM: Robust poll creation with proper validation and atomicity.The
CreatePollmethod properly validates inputs, deduplicates options, initializes counts to zero, stores metadata as JSON, and uses a pipeline for atomicity. The error handling is comprehensive and contextual.
150-174: LGTM: Non-blocking SCAN replaces blocking KEYS.The code now uses
SCANwith a bounded count (100) to iterate over poll keys instead of the blockingKEYScommand. This prevents blocking Redis in production as the dataset grows.frontend/src/Pages/TeamDebateRoom.tsx (2)
356-370: LGTM: Proper ref synchronization to avoid stale closures.The effects correctly synchronize refs (
isTeam1Ref,myTeamIdRef,debatePhaseRef,currentUserIdRef) with their corresponding state values. This prevents stale closure issues in WebSocket handlers when the effect dependency array is minimized.
374-392: LGTM: Media initialization extracted to a callback.Extracting
getMediaas auseCallbackallows it to be invoked from the WebSocketonopenhandler without recreating the effect. The error handling properly setsmediaErroron failure.frontend/src/Pages/OnlineDebateRoom.tsx (6)
143-156: LGTM: Clean integration of spectator and audience features.The component properly imports and uses the shared debate WebSocket hook (
useDebateWS) along with Jotai atoms for presence, questions, and polls. The memoized derived values (recentQuestions,audiencePolls) are efficient.
239-279: LGTM: Comprehensive spectator connection cleanup.The cleanup callbacks properly close peer connections, clear pending candidates, maintain base-ID tracking, and filter offer queues. The error handling silently ignores close errors, which is appropriate for cleanup paths.
293-377: LGTM: Robust spectator offer initiation with proper error handling.The
startSpectatorOffercallback validates WebSocket/stream/user readiness, normalizes connection IDs, creates RTCPeerConnection with proper event handlers, and cleans up on errors. The ICE candidate handler correctly uses the liveuserIdfrom the ref.
379-417: LGTM: Queue-based spectator offer management.The queue and flush logic ensures offers are only sent when the local stream and user ID are ready. The deduplication by connectionId prevents redundant offers.
2454-2531: LGTM: Clean audience interaction UI with polls and comments.The Audience Polls and Audience Comments panels properly display poll results with vote counts and recent questions with timestamps. The empty states guide spectators to use the viewer portal.
2532-2541: LGTM: Clear spectator presence indicator.The spectator count display at the bottom provides clear visibility of the current audience size.
backend/websocket/team_websocket.go (5)
256-260: Good error handling for team status fetch.The fallback to an empty status map when
GetTeamSpeakingStatusfails allows the connection to proceed gracefully rather than crashing. The error is logged for debugging. This pattern is consistently applied throughout the file.
410-411: Excellent fix for concurrent map access.Using
snapshotTeamRecipientsinstead of directly iteratingroom.Clientseliminates the race condition that could cause panics. The helper function properly holds the mutex while building the snapshot, then releases it before iterating to send messages.
769-776: Token consumption logic looks correct.The pattern of calling
TryConsumeForSpeakingto atomically check and consume tokens, then updatingclient.Tokenswith the returnedremainingTokensunder mutex protection is sound. This ensures the client's local token count stays in sync with the service's token bucket.
789-795: Well-structured status broadcast.This segment correctly combines error-safe status fetching, race-free recipient snapshotting, and team-filtered broadcasting. The pattern is consistent with other handlers in the file.
549-550: Comprehensive fix for safe broadcasting.All broadcast handlers (
handleTeamPhaseChange,handleTeamTopicChange,handleTeamRoleSelection,handleTeamTurnEnd) now consistently usesnapshotTeamRecipientsto avoid concurrent map access issues. This systematic approach eliminates race conditions across the entire file.Also applies to: 567-568, 603-604, 831-832
backend/websocket/websocket.go (6)
119-154: Well-designed helper functions for spectator support.The new helpers (
nonSpectatorRecipients,countDebaters,countSpectators) provide clean abstractions for filtering and counting clients by type. Each properly holds the room mutex during access, preventing race conditions.
195-219: Good privacy boundary for spectator notifications.The
notifySpectatorStatusfunction correctly sends spectator join/leave events only to debaters (vianonSpectatorRecipients), preventing spectators from learning about each other while giving debaters visibility into their audience.
327-330: Proper spectator identification setup.Using UUID for
ConnectionIDensures uniqueness across spectator connections, which is essential for WebRTC signaling with multiple spectators. SettingRole = "spectator"provides a clear type distinction.
398-426: Robust disconnect handling with proper cleanup.The enhanced error handling distinguishes normal closures from unexpected errors in logs, properly tracks the disconnected client before deletion to enable post-removal notifications, and maintains consistency by broadcasting updated participant lists to remaining clients.
475-490: Well-structured spectator offer request flow.The handler correctly enriches spectator
requestOffermessages with identifying information (connectionId, userId, displayName) and broadcasts only to debaters usingbroadcastRawToDebaters. The fallback to generate a ConnectionID ensures robustness.
666-682: Double unlock issue resolved.The
defer room.Mutex.Unlock()pattern correctly prevents the double unlock panic that was flagged in past reviews. The early return for spectators now safely exits without unlocking twice. The addedbroadcastParticipantscall ensures all clients see updated role information.
| case 'vote': | ||
| setPollState((prev) => { | ||
| const newState = { ...prev }; | ||
| const pollId = eventData.payload.pollId; | ||
| const option = eventData.payload.option; | ||
| if (!newState[pollId]) { | ||
| newState[pollId] = {}; | ||
| const pollId = eventData.payload?.pollId; | ||
| const option = eventData.payload?.option; | ||
| if (typeof pollId !== 'string' || typeof option !== 'string') { | ||
| return prev; | ||
| } | ||
| const nextState = { ...prev }; | ||
| const existing = nextState[pollId]; | ||
| if (!existing) { | ||
| nextState[pollId] = { | ||
| pollId, | ||
| question: '', | ||
| options: [option], | ||
| counts: { [option]: 1 }, | ||
| voters: 0, | ||
| }; | ||
| return nextState; | ||
| } | ||
| newState[pollId][option] = | ||
| (newState[pollId][option] || 0) + 1; | ||
| return newState; | ||
| const nextCounts = { ...existing.counts }; | ||
| nextCounts[option] = (nextCounts[option] || 0) + 1; | ||
| const nextOptions = existing.options.includes(option) | ||
| ? existing.options | ||
| : [...existing.options, option]; | ||
| nextState[pollId] = { | ||
| ...existing, | ||
| options: nextOptions, | ||
| counts: nextCounts, | ||
| }; | ||
| return nextState; | ||
| }); | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vote handler creates polls on-the-fly without validation.
The vote handler creates a new poll entry if the pollId doesn't exist (lines 204-211). This could allow votes for non-existent polls to create stub entries with incomplete metadata. Typically, only poll_created or poll_snapshot should establish poll entries.
Consider rejecting votes for unknown polls:
case 'vote':
setPollState((prev) => {
const pollId = eventData.payload?.pollId;
const option = eventData.payload?.option;
if (typeof pollId !== 'string' || typeof option !== 'string') {
return prev;
}
const nextState = { ...prev };
const existing = nextState[pollId];
if (!existing) {
- nextState[pollId] = {
- pollId,
- question: '',
- options: [option],
- counts: { [option]: 1 },
- voters: 0,
- };
- return nextState;
+ // Vote for unknown poll - ignore it
+ console.warn(`Received vote for unknown poll: ${pollId}`);
+ return prev;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case 'vote': | |
| setPollState((prev) => { | |
| const newState = { ...prev }; | |
| const pollId = eventData.payload.pollId; | |
| const option = eventData.payload.option; | |
| if (!newState[pollId]) { | |
| newState[pollId] = {}; | |
| const pollId = eventData.payload?.pollId; | |
| const option = eventData.payload?.option; | |
| if (typeof pollId !== 'string' || typeof option !== 'string') { | |
| return prev; | |
| } | |
| const nextState = { ...prev }; | |
| const existing = nextState[pollId]; | |
| if (!existing) { | |
| nextState[pollId] = { | |
| pollId, | |
| question: '', | |
| options: [option], | |
| counts: { [option]: 1 }, | |
| voters: 0, | |
| }; | |
| return nextState; | |
| } | |
| newState[pollId][option] = | |
| (newState[pollId][option] || 0) + 1; | |
| return newState; | |
| const nextCounts = { ...existing.counts }; | |
| nextCounts[option] = (nextCounts[option] || 0) + 1; | |
| const nextOptions = existing.options.includes(option) | |
| ? existing.options | |
| : [...existing.options, option]; | |
| nextState[pollId] = { | |
| ...existing, | |
| options: nextOptions, | |
| counts: nextCounts, | |
| }; | |
| return nextState; | |
| }); | |
| break; | |
| case 'vote': | |
| setPollState((prev) => { | |
| const pollId = eventData.payload?.pollId; | |
| const option = eventData.payload?.option; | |
| if (typeof pollId !== 'string' || typeof option !== 'string') { | |
| return prev; | |
| } | |
| const nextState = { ...prev }; | |
| const existing = nextState[pollId]; | |
| if (!existing) { | |
| // Vote for unknown poll - ignore it | |
| console.warn(`Received vote for unknown poll: ${pollId}`); | |
| return prev; | |
| } | |
| const nextCounts = { ...existing.counts }; | |
| nextCounts[option] = (nextCounts[option] || 0) + 1; | |
| const nextOptions = existing.options.includes(option) | |
| ? existing.options | |
| : [...existing.options, option]; | |
| nextState[pollId] = { | |
| ...existing, | |
| options: nextOptions, | |
| counts: nextCounts, | |
| }; | |
| return nextState; | |
| }); | |
| break; |
🤖 Prompt for AI Agents
In frontend/src/hooks/useDebateWS.ts around lines 194 to 225, the 'vote' case
currently creates a new poll stub when pollId is unknown; instead, change the
handler to ignore/reject votes for unknown polls (do not create a new poll
object) and optionally log or trigger a fetch/request for the full poll
snapshot; specifically, inside setPollState, if pollId exists but there's no
existing entry return prev (and avoid constructing nextState with incomplete
metadata), and if desired emit a warning or enqueue a request to retrieve the
poll_snapshot/poll_created before accepting future votes.
| const API_BASE_URL = | ||
| import.meta.env.VITE_API_BASE_URL ?? "http://localhost:1313"; | ||
| import.meta.env.VITE_API_URL?.replace(/\/+$/, "") ?? "http://localhost:1313"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the environment variable rename is applied consistently across the codebase.
The environment variable has been renamed from VITE_API_BASE_URL to VITE_API_URL. This is a breaking configuration change that requires:
- All other service files in the frontend to use the same variable name
- Environment files (
.env,.env.example, etc.) to be updated - Documentation and setup instructions to reflect the new variable name
Additionally, this change appears unrelated to the PR's stated objectives (Audience Interaction with Live Polls, Anonymous Q&A). Consider whether this refactoring should be in a separate PR for clearer change tracking.
Run the following script to verify consistency:
🏁 Script executed:
#!/bin/bash
# Description: Check for inconsistent API URL environment variable usage across frontend services
# Search for any remaining references to VITE_API_BASE_URL
echo "=== Checking for old VITE_API_BASE_URL references ==="
rg -n "VITE_API_BASE_URL" frontend/
# Search for VITE_API_URL usage to confirm consistency
echo -e "\n=== Checking VITE_API_URL usage across services ==="
rg -n "VITE_API_URL" frontend/src/services/
# Check environment files
echo -e "\n=== Checking environment configuration files ==="
fd -e env -e example . frontend/ --exec cat {}Length of output: 400
🏁 Script executed:
#!/bin/bash
# Comprehensive verification of environment variable usage and configuration
# Check for .env files at repository root
echo "=== Environment files at repository root ==="
fd -e env -e example . . --maxdepth 1 2>/dev/null || echo "No env files found at root"
# List all service files in frontend/src/services/
echo -e "\n=== All service files in frontend/src/services/ ==="
fd '\.ts$' frontend/src/services/ 2>/dev/null
# Check each service file for API URL configuration
echo -e "\n=== API URL configuration in each service ==="
fd '\.ts$' frontend/src/services/ -x grep -n "import.meta.env.VITE_API" {} + 2>/dev/null || echo "No matches found"
# Search for documentation about environment variables
echo -e "\n=== Documentation files mentioning VITE_API ==="
fd -e md -e txt -e rst . . --exec grep -l "VITE_API" {} \; 2>/dev/null || echo "No documentation found"
# Check if there's a .env file template or example
echo -e "\n=== Checking for example/template config files ==="
find . -name ".env*" -o -name "*example*" -o -name "*.example" 2>/dev/null | head -20Length of output: 767
🏁 Script executed:
#!/bin/bash
# Check API configuration in the four non-updated service files
echo "=== leaderboardService.ts API configuration ==="
grep -n "API\|import.meta.env\|fetch\|http" frontend/src/services/leaderboardService.ts | head -10
echo -e "\n=== profileService.ts API configuration ==="
grep -n "API\|import.meta.env\|fetch\|http" frontend/src/services/profileService.ts | head -10
echo -e "\n=== transcriptService.ts API configuration ==="
grep -n "API\|import.meta.env\|fetch\|http" frontend/src/services/transcriptService.ts | head -10
echo -e "\n=== vsbot.ts API configuration ==="
grep -n "API\|import.meta.env\|fetch\|http" frontend/src/services/vsbot.ts | head -10Length of output: 1776
Incomplete environment variable migration creates critical inconsistency across services.
The PR updates only 2 of 6 frontend services to use VITE_API_URL, while 4 services still reference VITE_BASE_URL:
- Updated to
VITE_API_URL: teamService.ts, teamDebateService.ts - Still using
VITE_BASE_URL: leaderboardService.ts, profileService.ts, transcriptService.ts, vsbot.ts
This creates two problems:
- Broken configuration: If
VITE_API_URLandVITE_BASE_URLhave different values, services will fail to connect to the API - Inconsistent URL normalization: The
VITE_API_URLservices include.replace(/\/+$/, "")to remove trailing slashes, whileVITE_BASE_URLservices don't, leading to subtle bugs
Required fix: Either update all 6 services to use VITE_API_URL with the trailing slash normalization, or revert teamService.ts and teamDebateService.ts back to VITE_BASE_URL to maintain consistency.
🤖 Prompt for AI Agents
In frontend/src/services/teamService.ts around lines 2-3 (and apply to
teamDebateService.ts, leaderboardService.ts, profileService.ts,
transcriptService.ts, vsbot.ts), replace any usage of
import.meta.env.VITE_BASE_URL with import.meta.env.VITE_API_URL?.replace(/\/+$/,
"") ?? "http://localhost:1313" so all services use the same env var and
normalize trailing slashes; remove VITE_BASE_URL references and ensure the
fallback host remains the same across all six files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
frontend/src/services/teamDebateService.ts (1)
151-165: Fix type mismatch on line 163.The function signature declares a return type of
TeamMatchmakingPoolResponse, but line 163 referencesMatchmakingPoolResponse, which was removed according to the changes. This will cause a TypeScript compilation error.Apply this diff to fix the type mismatch:
- const data: MatchmakingPoolResponse = await response.json(); + const data: TeamMatchmakingPoolResponse = await response.json();backend/websocket/team_websocket.go (5)
566-577: Resolve merge conflict in phase change broadcast.Same unresolved merge conflict. Use
snapshotAllTeamClients(room)for broadcasting to all clients including the sender.-<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main + for _, r := range snapshotAllTeamClients(room) {
589-598: Resolve merge conflict in topic change broadcast.-<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main + for _, r := range snapshotAllTeamClients(room) {
629-641: Resolve merge conflict in role selection broadcast.-<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main + for _, r := range snapshotAllTeamClients(room) {
819-841: Resolve merge conflict and ensurecurrentTurnis declared.This merge conflict needs careful resolution. The HEAD branch has good error handling but doesn't declare
currentTurn, while the main branch declares it but lacks error handling. Line 836 usescurrentTurn, which would be undefined if you only keep the HEAD branch.-<<<<<<< HEAD - teamStatus, statusErr := room.TokenBucket.GetTeamSpeakingStatus(client.TeamID, room.TurnManager) - if statusErr != nil { - log.Printf("failed to load team status for team %s: %v", client.TeamID.Hex(), statusErr) - teamStatus = map[string]interface{}{} - } - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - teamStatus := room.TokenBucket.GetTeamSpeakingStatus(client.TeamID, room.TurnManager) - currentTurn := room.TurnManager.GetCurrentTurn(client.TeamID).Hex() - for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main + teamStatus, statusErr := room.TokenBucket.GetTeamSpeakingStatus(client.TeamID, room.TurnManager) + if statusErr != nil { + log.Printf("failed to load team status for team %s: %v", client.TeamID.Hex(), statusErr) + teamStatus = map[string]interface{}{} + } + currentTurn := room.TurnManager.GetCurrentTurn(client.TeamID).Hex() + for _, r := range snapshotAllTeamClients(room) {
867-883: Resolve merge conflict in turn end broadcast.-<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main + for _, r := range snapshotAllTeamClients(room) {
♻️ Duplicate comments (3)
backend/websocket/team_websocket.go (1)
192-198: Initialization errors are still being ignored.The previous review flagged that
InitializeTeamTurnsandInitializeTeamBucketserrors should be handled to fail fast before entering the WebSocket loop. This issue remains unaddressed—if these calls fail, the room operates with empty turn order and missing buckets, causingGetCurrentTurnto returnNilandTryConsumeForSpeakingto deny all speakers.frontend/src/Pages/TeamDebateRoom.tsx (2)
1029-1029: Replace hardcoded localhost with BASE_URL.Line 1029 still uses
http://localhost:1313/submit-transcriptsdespite previous review feedback. This endpoint will fail in deployed environments. Use theBASE_URLconstant (defined at lines 127-129) that's already being used correctly for WebSocket connections at lines 421-426.Apply this diff:
- const response = await fetch(`http://localhost:1313/submit-transcripts`, { + const response = await fetch(`${BASE_URL}/submit-transcripts`, {
1091-1099: Remove duplicate toggleCamera declaration.
toggleCamerais already defined at lines 936-954 usinguseCallback. This redeclaration at lines 1091-1099 will cause a runtime error and is flagged by the linter.Apply this diff to remove the duplicate:
- const toggleCamera = () => { - const stream = localStreamRef.current; - const videoTrack = stream?.getVideoTracks()[0]; - const nextState = !isCameraOn; - if (videoTrack) { - videoTrack.enabled = nextState; - } - setIsCameraOn(nextState); - }; - // Manage setup popup visibility and check if debate should start
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
backend/websocket/team_websocket.go(8 hunks)frontend/src/Pages/TeamDebateRoom.tsx(5 hunks)frontend/src/services/teamDebateService.ts(3 hunks)frontend/src/services/teamService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/services/teamService.ts
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/services/teamDebateService.ts (2)
frontend/src/services/teamService.ts (1)
Team(7-18)backend/models/team.go (1)
Team(11-22)
🪛 Biome (2.1.2)
frontend/src/Pages/TeamDebateRoom.tsx
[error] 1091-1091: Shouldn't redeclare 'toggleCamera'. Consider to delete it or rename it.
'toggleCamera' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (8)
frontend/src/services/teamDebateService.ts (4)
2-2: LGTM!The import correctly references the Team type needed for the
MatchmakingStatusResponseinterface.
43-51: LGTM!The interface is well-structured with appropriate fields for representing a team entry in the matchmaking pool.
53-56: LGTM!The wrapper interface correctly structures the matchmaking pool response with pool size and team entries.
58-62: LGTM!The interface appropriately models the matchmaking status response with optional team and match ID fields.
backend/websocket/team_websocket.go (4)
257-261: Good error handling with safe fallback.Logging the error and falling back to an empty map prevents nil-related panics downstream while still surfacing the issue in logs for debugging.
415-419: Consistent error handling pattern.
799-806: Token consumption logic looks correct.The
TryConsumeForSpeakingcall properly returns both permission and remaining tokens, which are correctly used to update client state.
860-864: Consistent error handling pattern.
| <<<<<<< HEAD | ||
| recipients := snapshotTeamRecipients(room, nil) | ||
| for _, r := range recipients { | ||
| ======= | ||
| for _, r := range snapshotAllTeamClients(room) { | ||
| >>>>>>> main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unresolved merge conflict markers will prevent compilation.
The file contains unresolved Git merge conflict markers. This code will not compile. You need to resolve these conflicts by choosing one approach. Given that this broadcasts to all clients (including sender for sync), snapshotAllTeamClients(room) is semantically clearer.
-<<<<<<< HEAD
- recipients := snapshotTeamRecipients(room, nil)
- for _, r := range recipients {
-=======
- for _, r := range snapshotAllTeamClients(room) {
->>>>>>> main
+ for _, r := range snapshotAllTeamClients(room) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <<<<<<< HEAD | |
| recipients := snapshotTeamRecipients(room, nil) | |
| for _, r := range recipients { | |
| ======= | |
| for _, r := range snapshotAllTeamClients(room) { | |
| >>>>>>> main | |
| for _, r := range snapshotAllTeamClients(room) { |
🤖 Prompt for AI Agents
In backend/websocket/team_websocket.go around lines 422 to 427 there are
unresolved Git merge conflict markers (<<<<<<<, =======, >>>>>>>) that will
prevent compilation; remove the conflict markers and the older branch code,
replace the recipients-based loop with the clearer call to
snapshotAllTeamClients(room) (i.e., iterate over snapshotAllTeamClients(room)
directly), delete the unused recipients variable, and ensure the final code
contains only one for-loop using snapshotAllTeamClients(room) so the file
compiles and broadcasts to all clients including the sender.
| useEffect(() => { | ||
| isTeam1Ref.current = isTeam1; | ||
| }, [isTeam1]); | ||
|
|
||
| useEffect(() => { | ||
| myTeamIdRef.current = myTeamId; | ||
| }, [myTeamId]); | ||
|
|
||
| useEffect(() => { | ||
| debatePhaseRef.current = debatePhase; | ||
| }, [debatePhase]); | ||
|
|
||
| useEffect(() => { | ||
| currentUserIdRef.current = currentUser?.id; | ||
| }, [currentUser?.id]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate useEffect blocks.
Lines 368-382 contain four useEffect hooks that are exact duplicates of the ones already defined at lines 182-184, 192-194, 196-199, and 201-204. This duplication will cause each ref-sync effect to execute twice on every state change, leading to unnecessary re-renders and incorrect behavior.
Apply this diff to remove the duplicates:
- useEffect(() => {
- isTeam1Ref.current = isTeam1;
- }, [isTeam1]);
-
- useEffect(() => {
- myTeamIdRef.current = myTeamId;
- }, [myTeamId]);
-
- useEffect(() => {
- debatePhaseRef.current = debatePhase;
- }, [debatePhase]);
-
- useEffect(() => {
- currentUserIdRef.current = currentUser?.id;
- }, [currentUser?.id]);
-
// Initialize WebSocket connection - only need token and debateId📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| isTeam1Ref.current = isTeam1; | |
| }, [isTeam1]); | |
| useEffect(() => { | |
| myTeamIdRef.current = myTeamId; | |
| }, [myTeamId]); | |
| useEffect(() => { | |
| debatePhaseRef.current = debatePhase; | |
| }, [debatePhase]); | |
| useEffect(() => { | |
| currentUserIdRef.current = currentUser?.id; | |
| }, [currentUser?.id]); | |
| // Initialize WebSocket connection - only need token and debateId |
🤖 Prompt for AI Agents
frontend/src/Pages/TeamDebateRoom.tsx lines 368-382: the four useEffect hooks
here are duplicates of the ref-sync effects already declared at lines ~182-204,
causing them to run twice; remove the duplicated useEffect blocks (the ones
syncing isTeam1Ref, myTeamIdRef, debatePhaseRef, and currentUserIdRef) from
lines 368-382 so each ref is updated by a single effect, and ensure no other
logic depends on the removed copies.
| {speechError && ( | ||
| <p className="text-amber-600 mt-2 text-center max-w-6xl mx-auto"> | ||
| {speechError} | ||
| </p> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix undefined speechError reference.
Lines 1697-1701 reference speechError, but line 214 declares the state without capturing the value: const [, setSpeechError] = useState<string | null>(null);. The empty position before the comma means speechError is not defined in scope, causing a runtime ReferenceError.
Apply this diff to capture the state value:
- const [, setSpeechError] = useState<string | null>(null);
+ const [speechError, setSpeechError] = useState<string | null>(null);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {speechError && ( | |
| <p className="text-amber-600 mt-2 text-center max-w-6xl mx-auto"> | |
| {speechError} | |
| </p> | |
| )} | |
| const [speechError, setSpeechError] = useState<string | null>(null); |
🤖 Prompt for AI Agents
In frontend/src/Pages/TeamDebateRoom.tsx around lines 1697-1701 and the state
declaration at ~line 214, the component renders a speechError variable that was
never captured from useState (const [, setSpeechError] = ...), causing a
ReferenceError; fix it by updating the state declaration to capture both value
and setter (const [speechError, setSpeechError] = useState<string | null>(null))
so the rendered {speechError} is defined, and ensure any other local references
use the captured speechError variable.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.