feat: refine presentation mode speech bubbles, input flow, and accessibility#195
Conversation
71cd3aa to
ed1fdff
Compare
There was a problem hiding this comment.
Code Review
Overall quality is solid — feature-complete, animations are polished, i18n is thorough. A few issues to address:
Issues
1. Duplicate AvatarDisplay component
- Defined separately in both
presentation-speech-overlay.tsx:33-39androundtable/index.tsx:88-100with slightly different signatures (the latter accepts aclassNameprop). - Suggest extracting to a shared module (e.g.
components/ui/avatar-display.tsx) to avoid maintaining two copies.
2. Math.random() called during render causes animation jitter
- In
roundtable/index.tsx:619,624(waveform bar animations),Math.random()generates new values on every re-render, making animation parameters unstable and causing visual jumps. - Suggest pre-generating random values with
useMemoor defining a fixed array outside the component.
3. setTimeout not cleaned up on unmount
roundtable/index.tsx:280-282and299-301:setTimeout(() => setUserMessage(null), 3000)is never cleared on component unmount, which can trigger setState on an unmounted component.- Suggest storing the timer ID in a
useRefand callingclearTimeoutin a cleanup function.
4. Incomplete useEffect dependency array
roundtable/index.tsx:218-222: the effect readsuserMessageinside its body but doesn't include it in the dependency array. Even if intentional (only fire whenplaybackView.sourceTextorthinkingStatechanges), this will be flagged byreact-hooks/exhaustive-deps.- Suggest either adding
userMessageto the deps or adding an eslint-disable comment explaining the intent.
5. useAgentRegistry.getState() called during render
roundtable/index.tsx:439-441: Zustand'sgetState()is called directly in the render body. While not a Rules-of-Hooks violation, it doesn't subscribe to state changes — if the agent registry updates at runtime, this won't trigger a re-render.- If this is intentional for performance (agent config rarely changes), a brief comment explaining the choice would help.
6. Bubble width jitter during streaming
presentation-speech-overlay.tsx:127: the bubble card usesmin-w-[220px] max-w-full, and the outer container caps atmax-w-[min(420px,...)]. This means the bubble starts narrow and expands as text streams in, causing noticeable width fluctuation that makes the text hard to read while it's being generated.- The PR description only mentions slide/fade transitions — this width growth doesn't appear to be an intentional design choice.
- Suggest changing
min-w-[220px]tow-full(or setting a fixed width matching the outermax-w) so the bubble maintains a consistent width from the start.
Minor / Nits
roundtable/index.tsxis now 1881 lines. The presentation mode branch alone is ~360 lines. Long-term, consider extracting the presentation mode UI into its own component.whiteboard-canvas.tsxfullscreen detection change (top-3vsbottom-3) is clean and minimal.canvas-toolbar.tsxgap-2spacing and fullscreen toggle button are well done.- i18n keys are complete for both zh-CN and en-US — no omissions found.
Verdict
Feature implementation is complete, animation quality is high, i18n is thorough. Recommend addressing 1–6 above before merging.
Additional feedback: Fullscreen button placementThe fullscreen toggle is currently in the right-side area next to the Chat toggle. I'd suggest moving it to the center toolbar (after the whiteboard button) instead. Here's the reasoning: Current placement (right side, next to Chat toggle):
Suggested placement (center toolbar, near whiteboard/auto-play):
Suggested position: right after the whiteboard button, before the right-side section. |
Thanks for the thorough review, @cosarah! All 6 issues are valid — will address them in the next commit on this PR. Plan:
Re: fullscreen button placement — Agree with moving it to the center toolbar. The reasoning about discoverability and consistency with mainstream tools makes sense. Will update in the same commit.
|
Follow-up on review points #4 and #5After a second look, I want to correct/soften two points from my earlier review: #4 — My suggestion to add #5 — This is a standard Zustand pattern for grabbing a one-time snapshot of rarely-changing data. It's fine as-is — my original comment was overly nitpicky. Feel free to ignore. |
|
All 6 issues + the fullscreen placement suggestion have been addressed across the follow-up commits !!!!!!!!😊😊😊 Issues 1–6
Fullscreen buttonMoved from right-side (next to Chat toggle) to center toolbar, right after the whiteboard button. Consistent in both normal and fullscreen modes. Minor / Nits
Additional fixes from auditAlso addressed a few edge cases found during deep audit:
|
For 4 and 5 — just saw your follow-up, appreciate the correction! I went ahead and addressed them anyway since the refactoring improves the code regardless:
|
cosarah
left a comment
There was a problem hiding this comment.
Thanks! LGTM—all previous issues have been addressed.
However, given the scale of this change and the significant design implications, I need to get final confirmation from @wyuc tomorrow before we can merge.
Thanks again for your hard work and contribution!
UI FeedbackGreat work on the presentation mode! Tested locally and the overall experience is solid. A few UI-level suggestions: 1. Fullscreen button placementI see the button was moved to the center toolbar per @cosarah's earlier suggestion. I think the reasoning about discoverability makes sense, but on second thought I'd prefer keeping it on the right side — fullscreen is more of a view mode toggle than a playback action, and mainstream references (YouTube, Bilibili, Zoom) consistently place it in the bottom-right area. Having it among play/pause/speed controls feels a bit crowded. What do you both think? 2. Double-layer toolbar in light modeIn fullscreen + light mode, the outer pill container nests the inner center controls container, creating a gray-on-gray double-border effect that looks a bit heavy. Dark mode handles it better since the contrast is lower. Suggestion: in fullscreen mode, consider removing the inner container's background/border so all buttons sit directly inside the outer pill — single visual layer.
3. Bubble opacity on small screensLight-mode bubbles are quite opaque ( 4. Student agent avatarThe left side shows teacher avatar + name nicely, but when a student agent is speaking on the right side, only the user's own avatar is visible in the dock. Showing the student agent's avatar alongside the right-side bubble would make the role-dialogue feel more complete and symmetric. |
|
All points are valid, thanks for the detailed review. Working on fixes now! |
|
Hi @wyuc ! All points have been addressed:
Please take another look — happy to iterate further if needed~ |
3272bc3 to
4f2ed80
Compare
4f2ed80 to
5db55fd
Compare
Code Review (Round 2)Two issues worth addressing before merge: 1. [Major] Spacebar handler conflict in presentation modeBoth Suggestion: disable Roundtable's spacebar handler when 2. [Major]
|
|
@wyuc Thanks~ Here's an update on each item: Major #1 — Spacebar handler conflict: The reason is that...Roundtable's buffer-level pause actually woks in presentation mode (and users expect Space to pause the discussion stream, not the engine — the engine is in live mode during discussions anyway). This makes the conflict resolution deterministic without depending on addEventListener ordering. Major #2 — handlePlayPause useCallback: Done |
f29b1d8 to
3197794
Compare
|
Sorry, should have caught this earlier in testing: the ProactiveCard (proactive discussion prompt) doesn't appear in presentation mode. When a discussion trigger fires during fullscreen playback, the card that normally pops up in the roundtable area to ask "do you want to join this discussion?" is not visible. You have to exit fullscreen to see it and interact with it. The ProactiveCard is rendered inside the dock (line ~926), and the dock visibility logic ( This is a functional gap since users in presentation mode would miss discussion opportunities entirely. Could you take a look? |
Brooo okay this is actually embarrassing — can't believe I missed this!🤣 The ProactiveCard is literally rendered inside the dock, the dock visibility check includes !!discussionRequest... and yet somehow I never actually tested triggering a discussion while in fullscreen. Major oversight on my part! I'm at Starbucks right now without my machine, so I can only poke around the code on GitHub for now. From what I can see, my best guess is that it's an AnimatePresence keying issue, or the anchor ref is stale when the fullscreen layout switches...? Not quite sure about that. Will push a proper fix later once I'm back at my desk~! |
Take your time, no worries at all~ |
…navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102
…ibility - User messages now display as right-side speech bubbles for send confirmation - 'Your turn' cue is a clickable glass button respecting ASR preference - Cue properly excludes bubbleRole/thinkingState to avoid UI overlap - Right-side dock + bubble uses flexbox for dynamic stacking - Voice-to-text switch now stops recording to prevent mic leaks - Backdrop no longer blocks toolbar (bottom-14 cutoff) - User message clears immediately when agent starts responding - Dock buttons and avatar have proper aria-labels and button semantics - Voice panel mic trigger converted from div to button with aria-label - Added 5 i18n keys (zh-CN + en-US) for new accessible labels - matchesSide typed as boolean, removed unused userAvatar from left overlay - Avatar alt text localized via i18n Refs THU-MAIC#102
…ntConfig, fix a11y
…rs, bubble opacity 60%
…y waitUntilDrained pause behavior
- Stage: skip Space during active QA/discussion (let Roundtable own buffer-level pause); deterministic, no listener-order dependency - Stage: wrap handlePlayPause in useCallback([playbackCompleted, currentScene]) to avoid keyboard listener re-registration - Stage: clear presentationIdleTimerRef on unmount - PresentationBubbleCard: add aria-live='polite' for screen readers - Extract shared avatar constants to roundtable/constants.ts
63c04ab to
13bd7fe
Compare
Alright, found the root cause and pushed a fix~ It wasn't an AnimatePresence or anchor ref issue. ProactiveCard uses I Added a Changes: proactive-card.tsx, roundtable/index.tsx, stage.tsx. About 6 lines total. Also rebased onto latest main~ |
|
ProactiveCard is showing now, nice fix! One more thing though: in presentation mode, the card pops up anchored to the dock without any indication of which agent is initiating the discussion. In normal mode this is implicit (the card appears next to the agent's avatar), but in fullscreen that context is lost. Since the dock already has the slide-out agent avatar animation when an agent is speaking (the blue-ringed avatar that appears next to the user avatar), could we reuse that same mechanism here? When a discussion request comes in, slide out the requesting agent's avatar in the dock, so users can see who's asking before they click "Join". The props are already being passed ( |
Done in 7bc5a8e~ The dock now slides out the requesting agent's avatar (reusing the existing blue-ring AnimatePresence animation) when discussionRequest is active. |
wyuc
left a comment
There was a problem hiding this comment.
Reviewed the presentation mode changes, fullscreen lifecycle, and keyboard handling. No blocking issues. Will open a follow-up issue to polish bubble interactions and ProactiveCard anchoring.
…ibility (THU-MAIC#195) * feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 * feat: refine presentation mode speech bubbles, input flow, and accessibility - User messages now display as right-side speech bubbles for send confirmation - 'Your turn' cue is a clickable glass button respecting ASR preference - Cue properly excludes bubbleRole/thinkingState to avoid UI overlap - Right-side dock + bubble uses flexbox for dynamic stacking - Voice-to-text switch now stops recording to prevent mic leaks - Backdrop no longer blocks toolbar (bottom-14 cutoff) - User message clears immediately when agent starts responding - Dock buttons and avatar have proper aria-labels and button semantics - Voice panel mic trigger converted from div to button with aria-label - Added 5 i18n keys (zh-CN + en-US) for new accessible labels - matchesSide typed as boolean, removed unused userAvatar from left overlay - Avatar alt text localized via i18n Refs THU-MAIC#102 * fix: address code review audit cancel ghost sends, consolidate getAgentConfig, fix a11y * fix: guard concurrent voice recording and suppress cancel error toast * style: vertically center placeholder text in presentation input bar * style: full light-mode adaptation for presentation UI, toolbar dividers, bubble opacity 60% * chore: address THU-MAIC#129 follow-up remove orphan i18n keys, clarify waitUntilDrained pause behavior * fix: resolve spacebar conflict and address review feedback - Stage: skip Space during active QA/discussion (let Roundtable own buffer-level pause); deterministic, no listener-order dependency - Stage: wrap handlePlayPause in useCallback([playbackCompleted, currentScene]) to avoid keyboard listener re-registration - Stage: clear presentationIdleTimerRef on unmount - PresentationBubbleCard: add aria-live='polite' for screen readers - Extract shared avatar constants to roundtable/constants.ts * style: format with prettier * fix: render ProactiveCard inside fullscreen container via portalContainer prop * feat: show requesting agent avatar in dock on discussion request --------- Co-authored-by: YizukiAme <yizukiame@github.com>
…ibility (THU-MAIC#195) * feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 * feat: refine presentation mode speech bubbles, input flow, and accessibility - User messages now display as right-side speech bubbles for send confirmation - 'Your turn' cue is a clickable glass button respecting ASR preference - Cue properly excludes bubbleRole/thinkingState to avoid UI overlap - Right-side dock + bubble uses flexbox for dynamic stacking - Voice-to-text switch now stops recording to prevent mic leaks - Backdrop no longer blocks toolbar (bottom-14 cutoff) - User message clears immediately when agent starts responding - Dock buttons and avatar have proper aria-labels and button semantics - Voice panel mic trigger converted from div to button with aria-label - Added 5 i18n keys (zh-CN + en-US) for new accessible labels - matchesSide typed as boolean, removed unused userAvatar from left overlay - Avatar alt text localized via i18n Refs THU-MAIC#102 * fix: address code review audit cancel ghost sends, consolidate getAgentConfig, fix a11y * fix: guard concurrent voice recording and suppress cancel error toast * style: vertically center placeholder text in presentation input bar * style: full light-mode adaptation for presentation UI, toolbar dividers, bubble opacity 60% * chore: address THU-MAIC#129 follow-up remove orphan i18n keys, clarify waitUntilDrained pause behavior * fix: resolve spacebar conflict and address review feedback - Stage: skip Space during active QA/discussion (let Roundtable own buffer-level pause); deterministic, no listener-order dependency - Stage: wrap handlePlayPause in useCallback([playbackCompleted, currentScene]) to avoid keyboard listener re-registration - Stage: clear presentationIdleTimerRef on unmount - PresentationBubbleCard: add aria-live='polite' for screen readers - Extract shared avatar constants to roundtable/constants.ts * style: format with prettier * fix: render ProactiveCard inside fullscreen container via portalContainer prop * feat: show requesting agent avatar in dock on discussion request --------- Co-authored-by: YizukiAme <yizukiame@github.com>
…ibility (THU-MAIC#195) * feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 * feat: refine presentation mode speech bubbles, input flow, and accessibility - User messages now display as right-side speech bubbles for send confirmation - 'Your turn' cue is a clickable glass button respecting ASR preference - Cue properly excludes bubbleRole/thinkingState to avoid UI overlap - Right-side dock + bubble uses flexbox for dynamic stacking - Voice-to-text switch now stops recording to prevent mic leaks - Backdrop no longer blocks toolbar (bottom-14 cutoff) - User message clears immediately when agent starts responding - Dock buttons and avatar have proper aria-labels and button semantics - Voice panel mic trigger converted from div to button with aria-label - Added 5 i18n keys (zh-CN + en-US) for new accessible labels - matchesSide typed as boolean, removed unused userAvatar from left overlay - Avatar alt text localized via i18n Refs THU-MAIC#102 * fix: address code review audit cancel ghost sends, consolidate getAgentConfig, fix a11y * fix: guard concurrent voice recording and suppress cancel error toast * style: vertically center placeholder text in presentation input bar * style: full light-mode adaptation for presentation UI, toolbar dividers, bubble opacity 60% * chore: address THU-MAIC#129 follow-up remove orphan i18n keys, clarify waitUntilDrained pause behavior * fix: resolve spacebar conflict and address review feedback - Stage: skip Space during active QA/discussion (let Roundtable own buffer-level pause); deterministic, no listener-order dependency - Stage: wrap handlePlayPause in useCallback([playbackCompleted, currentScene]) to avoid keyboard listener re-registration - Stage: clear presentationIdleTimerRef on unmount - PresentationBubbleCard: add aria-live='polite' for screen readers - Extract shared avatar constants to roundtable/constants.ts * style: format with prettier * fix: render ProactiveCard inside fullscreen container via portalContainer prop * feat: show requesting agent avatar in dock on discussion request --------- Co-authored-by: YizukiAme <yizukiame@github.com>
…ibility (THU-MAIC#195) * feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top feat: add presentation mode with fullscreen, idle-hide, and keyboard navigation - Fullscreen via toolbar button or F11; exit via ESC/F11/button - Header auto-hides, sidebars collapse, slide fills viewport - Idle auto-hide (3s): toolbar/avatars fade out, speech bubble stays visible - Smart suspension: idle-hide pauses during typing/recording/voice input - Keyboard navigation: Arrow keys (prev/next), Space (play/pause), ESC (exit) - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 .git/COMMIT_EDITMSG [unix] (14:47 22/03/2026) 1,1 Top - F11 intercepted to use Fullscreen API (ESC-friendly) instead of browser native - Whiteboard hints reposition from bottom to top corners in fullscreen - i18n: fullscreen/exitFullscreen keys (zh-CN + en-US) Closes THU-MAIC#102 * feat: refine presentation mode speech bubbles, input flow, and accessibility - User messages now display as right-side speech bubbles for send confirmation - 'Your turn' cue is a clickable glass button respecting ASR preference - Cue properly excludes bubbleRole/thinkingState to avoid UI overlap - Right-side dock + bubble uses flexbox for dynamic stacking - Voice-to-text switch now stops recording to prevent mic leaks - Backdrop no longer blocks toolbar (bottom-14 cutoff) - User message clears immediately when agent starts responding - Dock buttons and avatar have proper aria-labels and button semantics - Voice panel mic trigger converted from div to button with aria-label - Added 5 i18n keys (zh-CN + en-US) for new accessible labels - matchesSide typed as boolean, removed unused userAvatar from left overlay - Avatar alt text localized via i18n Refs THU-MAIC#102 * fix: address code review audit cancel ghost sends, consolidate getAgentConfig, fix a11y * fix: guard concurrent voice recording and suppress cancel error toast * style: vertically center placeholder text in presentation input bar * style: full light-mode adaptation for presentation UI, toolbar dividers, bubble opacity 60% * chore: address THU-MAIC#129 follow-up remove orphan i18n keys, clarify waitUntilDrained pause behavior * fix: resolve spacebar conflict and address review feedback - Stage: skip Space during active QA/discussion (let Roundtable own buffer-level pause); deterministic, no listener-order dependency - Stage: wrap handlePlayPause in useCallback([playbackCompleted, currentScene]) to avoid keyboard listener re-registration - Stage: clear presentationIdleTimerRef on unmount - PresentationBubbleCard: add aria-live='polite' for screen readers - Extract shared avatar constants to roundtable/constants.ts * style: format with prettier * fix: render ProactiveCard inside fullscreen container via portalContainer prop * feat: show requesting agent avatar in dock on discussion request --------- Co-authored-by: YizukiAme <yizukiame@github.com>

Summary
Adds transient popup speech bubbles and a redesigned user interaction overlay for presentation mode, building on the fullscreen infrastructure from #133.
In fullscreen, the fixed 192px Roundtable strip is replaced with floating UI elements that overlay the slide canvas, following the UX direction discussed in #133 (comment):
(Demo video also posted on Lark/Feishu.)
For some reason I wasn't able to post the video here.
Changes
Speech Bubble Overlay
PresentationSpeechOverlay— dual-instance overlay (left + right side) that renders role-colored frosted-glass bubble cardsbuildPresentationBubbleModel()— pure function that mapsPlaybackView→ bubble props (role, avatar, name, text, loading state)AnimatePresence+motion.divwith slide + fade transitions; loading state shows animated dotslecturePlaying,lecturePaused,discussionActive, ordiscussionPausedphasesUser Input Flow (Presentation Mode)
AnimatePresenceAccessibility & Input Improvements
onPresentationInteractionChangecallback keeps the parentStageinformed when the user is actively typing/recording, so idle-hide correctly suspends during interactionProactiveCardis anchored to the floating dock instead of the standard roundtable, with agent avatar and color resolved from the agent registryToolbar
gap-2spacing between toolbar items for better touch targetsMaximize2/Minimize2) is now wired throughCanvasToolbar→CanvasArea→Stagei18n
New keys in
lib/i18n/stage.ts(zh-CN + en-US):stage.fullscreen/stage.exitFullscreenroundtable.voiceInput/voiceInputDisabled/textInput/stopRecording/startRecordingFiles Changed
components/roundtable/presentation-speech-overlay.tsxcomponents/roundtable/index.tsxcomponents/stage.tsxisPresenting,controlsVisible,onTogglePresentation,onPresentationInteractionChangeto Roundtablecomponents/canvas/canvas-area.tsxcomponents/canvas/canvas-toolbar.tsxgap-2layout fixlib/i18n/stage.tsDepends On
Refs #102
Scope Notes
Per reviewer feedback on #133:
whiteboard-canvas.tsxand not in scopeCloses #102
Closes #174