@@ -3,6 +3,7 @@ import { useKeyboard, useRenderer } from '@opentui/react'
33import React , { useCallback , useEffect , useMemo , useState } from 'react'
44
55import { AdBanner } from './ad-banner'
6+ import { Button } from './button'
67import { ChoiceAdBanner } from './choice-ad-banner'
78import { ShimmerText } from './shimmer-text'
89import { endFreebuffSessionBestEffort } from '../hooks/use-freebuff-session'
@@ -74,26 +75,41 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
7475 } )
7576
7677 // Always enable ads in the waiting room — this is where monetization lives.
77- const { ad, adData, recordImpression } = useGravityAd ( { enabled : true } )
78+ // forceStart bypasses the "wait for first user message" gate inside the hook,
79+ // which would otherwise block ads here since no conversation exists yet.
80+ const { ad, adData, recordImpression } = useGravityAd ( {
81+ enabled : true ,
82+ forceStart : true ,
83+ } )
84+
85+ // Release the seat + flush analytics before exit. Used by both Ctrl+C and
86+ // the top-right X button so they always do the same cleanup.
87+ const handleExit = useCallback ( ( ) => {
88+ const cleanup = Promise . allSettled ( [
89+ flushAnalytics ( ) ,
90+ endFreebuffSessionBestEffort ( ) ,
91+ ] )
92+ withTimeout ( cleanup , EXIT_CLEANUP_TIMEOUT_MS , undefined ) . finally ( ( ) => {
93+ process . exit ( 0 )
94+ } )
95+ } , [ ] )
7896
7997 // Ctrl+C exits. Stdin is in raw mode, so SIGINT never fires — the key comes
80- // through as a normal OpenTUI key event. Release the seat before exit so
81- // the next user in line doesn't have to wait for server-side expiry.
98+ // through as a normal OpenTUI key event.
8299 useKeyboard (
83- useCallback ( ( key : KeyEvent ) => {
84- if ( key . ctrl && key . name === 'c' ) {
85- key . preventDefault ?.( )
86- const cleanup = Promise . allSettled ( [
87- flushAnalytics ( ) ,
88- endFreebuffSessionBestEffort ( ) ,
89- ] )
90- withTimeout ( cleanup , EXIT_CLEANUP_TIMEOUT_MS , undefined ) . finally ( ( ) => {
91- process . exit ( 0 )
92- } )
93- }
94- } , [ ] ) ,
100+ useCallback (
101+ ( key : KeyEvent ) => {
102+ if ( key . ctrl && key . name === 'c' ) {
103+ key . preventDefault ?.( )
104+ handleExit ( )
105+ }
106+ } ,
107+ [ handleExit ] ,
108+ ) ,
95109 )
96110
111+ const [ exitHover , setExitHover ] = useState ( false )
112+
97113 // Elapsed-in-queue timer. Starts from `queuedAt` so it keeps ticking even if
98114 // the user wanders away and comes back.
99115 const queuedAtMs = useMemo ( ( ) => {
@@ -118,14 +134,45 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
118134 backgroundColor : theme . background ,
119135 } }
120136 >
137+ { /* Top-right exit affordance so mouse users have a clear way out even
138+ when they don't know Ctrl+C works. width: '100%' is required for
139+ justifyContent: 'flex-end' to actually push the X to the right. */ }
140+ < box
141+ style = { {
142+ width : '100%' ,
143+ flexDirection : 'row' ,
144+ justifyContent : 'flex-end' ,
145+ paddingTop : 1 ,
146+ paddingRight : 2 ,
147+ flexShrink : 0 ,
148+ } }
149+ >
150+ < Button
151+ onClick = { handleExit }
152+ onMouseOver = { ( ) => setExitHover ( true ) }
153+ onMouseOut = { ( ) => setExitHover ( false ) }
154+ style = { { paddingLeft : 1 , paddingRight : 1 } }
155+ >
156+ < text
157+ style = { { fg : exitHover ? theme . foreground : theme . muted } }
158+ attributes = { exitHover ? TextAttributes . BOLD : TextAttributes . NONE }
159+ >
160+ ✕
161+ </ text >
162+ </ Button >
163+ </ box >
164+
121165 < box
122166 style = { {
123167 flexGrow : 1 ,
124168 flexDirection : 'column' ,
125169 alignItems : 'center' ,
126- justifyContent : 'center' ,
170+ // flex-end so the logo + title + info clump sits just above the ad,
171+ // matching how chat anchors its header/messages to the input bar.
172+ justifyContent : 'flex-end' ,
127173 paddingLeft : 2 ,
128174 paddingRight : 2 ,
175+ paddingBottom : 1 ,
129176 gap : 1 ,
130177 } }
131178 >
@@ -165,9 +212,16 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
165212 } }
166213 >
167214 { session . position === 1 ? (
168- < text style = { { fg : theme . primary , alignSelf : 'flex-start' } } >
169- < ShimmerText text = "Next in line" />
170- </ text >
215+ < >
216+ < text style = { { fg : theme . primary , alignSelf : 'flex-start' } } >
217+ < ShimmerText text = "Next in line" />
218+ </ text >
219+ < text style = { { fg : theme . muted , alignSelf : 'flex-start' } } >
220+ { session . queueDepth === 1
221+ ? 'just you in line right now'
222+ : `${ session . queueDepth } people in line` }
223+ </ text >
224+ </ >
171225 ) : (
172226 < text style = { { fg : theme . foreground , alignSelf : 'flex-start' } } >
173227 < span fg = { theme . muted } > Position </ span >
0 commit comments