Skip to content

Commit c7f6969

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@89060a6324aca505419b0e16af5336a27cce1713
1 parent 7f9bb4e commit c7f6969

15 files changed

Lines changed: 85 additions & 103 deletions

bun.lock

Lines changed: 26 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/app.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FreebuffSupersededScreen } from './components/freebuff-superseded-scree
88
import { LoginModal } from './components/login-modal'
99
import { ProjectPickerScreen } from './components/project-picker-screen'
1010
import { TerminalLink } from './components/terminal-link'
11-
import { WaitingRoomScreen } from './components/waiting-room-screen'
11+
import { FreebuffLandingScreen } from './components/freebuff-landing-screen'
1212
import { useAuthQuery } from './hooks/use-auth-query'
1313
import { useAuthState } from './hooks/use-auth-state'
1414
import { useFreebuffSession } from './hooks/use-freebuff-session'
@@ -264,7 +264,7 @@ export const App = ({
264264
}
265265

266266
// Render project picker FIRST when at home directory or outside a project.
267-
// This deliberately precedes the login/auth and waiting-room gates so the
267+
// This deliberately precedes the login/auth and free-session gates so the
268268
// user always gets to pick a working directory before anything else — auth
269269
// failures or a banned freebuff session would otherwise replace the
270270
// picker mid-flash and look like being kicked out of the app.
@@ -343,7 +343,7 @@ interface AuthedSurfaceProps {
343343
}
344344

345345
/**
346-
* Rendered only after auth is confirmed. Owns the freebuff waiting-room gate
346+
* Rendered only after auth is confirmed. Owns the freebuff session gate
347347
* so `useFreebuffSession` runs exactly once per authed session (not before
348348
* we have a token).
349349
*/
@@ -397,12 +397,12 @@ const AuthedSurface = ({
397397
session.status === 'rate_limited' ||
398398
session.status === 'takeover_prompt')
399399
) {
400-
return <WaitingRoomScreen session={session} error={sessionError} />
400+
return <FreebuffLandingScreen session={session} error={sessionError} />
401401
}
402402

403403
// Chat history renders inside AuthedSurface so the freebuff session stays
404404
// mounted while the user browses history. Unmounting this surface would
405-
// DELETE the session row and drop the user back into the waiting room on
405+
// DELETE the session row and drop the user back onto the landing screen on
406406
// return.
407407
if (showChatHistory) {
408408
return (

cli/src/commands/command-registry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,8 @@ const ALL_COMMANDS: CommandDefinition[] = [
577577
}),
578578
// /end-session (freebuff-only) — end the active session early and drop back
579579
// to the model picker. The hook flips status to 'none', which unmounts
580-
// <Chat> and mounts <WaitingRoomScreen> on the landing view, where the
581-
// user picks a model and hits Enter to rejoin the queue.
580+
// <Chat> and mounts <FreebuffLandingScreen>, where the user picks a model
581+
// and hits Enter to start a new session.
582582
defineCommand({
583583
name: 'end-session',
584584
aliases: ['model'],

cli/src/components/ad-banner.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function columnWidths(count: number, availableWidth: number): number[] {
6464

6565
/**
6666
* A single ad card. Used full-width by {@link SingleAdBanner} (chat) and in a
67-
* row of columns by {@link ChoiceAdBanner} (waiting room). Manages its own
67+
* row of columns by {@link ChoiceAdBanner} (landing screen). Manages its own
6868
* hover state so each card highlights independently.
6969
*/
7070
const AdCard: React.FC<{
@@ -162,8 +162,8 @@ export const SingleAdBanner: React.FC<{
162162
}
163163

164164
/**
165-
* Up to four ads shown in a row. Still used by the freebuff waiting room, which
166-
* intentionally fills the space with multiple ads.
165+
* Up to four ads shown in a row. Still used by the freebuff landing screen,
166+
* which intentionally fills the space with multiple ads.
167167
*/
168168
export const ChoiceAdBanner: React.FC<ChoiceAdBannerProps> = ({
169169
ads,

cli/src/components/waiting-room-screen.tsx renamed to cli/src/components/freebuff-landing-screen.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import type { FreebuffSessionResponse } from '../types/freebuff-session'
4848
import type { FreebuffIpPrivacySignal } from '@codebuff/common/types/freebuff-session'
4949
import type { KeyEvent } from '@opentui/core'
5050

51-
interface WaitingRoomScreenProps {
51+
interface FreebuffLandingScreenProps {
5252
session: FreebuffSessionResponse | null
5353
error: string | null
5454
}
@@ -302,7 +302,7 @@ const StreakInlineLine: React.FC<{
302302
)
303303
}
304304

305-
export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
305+
export const FreebuffLandingScreen: React.FC<FreebuffLandingScreenProps> = ({
306306
session,
307307
error,
308308
}) => {
@@ -368,14 +368,16 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
368368
maxHeight: logoMode === 'full' ? undefined : 1,
369369
})
370370

371-
// Always enable ads in the waiting room — this is where monetization lives.
371+
// Always enable ads on the landing screen — this is where monetization lives.
372372
// forceStart bypasses the "wait for first user message" gate inside the hook,
373373
// which would otherwise block ads here since no conversation exists yet.
374374
// The server tries Gravity first, then falls back to ZeroClick and Carbon.
375375
const { ads, recordClick, recordImpression } = useGravityAd({
376376
enabled: true,
377377
forceStart: true,
378378
provider: 'gravity',
379+
// Legacy wire name for this surface — the ads API maps it to placements,
380+
// so it must not change with the component rename.
379381
surface: 'waiting_room',
380382
})
381383

@@ -391,7 +393,7 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
391393
accessTier === 'limited' && !compact ? getLimitedModeNotice(session) : null
392394
// 'none' = user hasn't started a session yet. We're in the pre-chat landing
393395
// state: show the picker with a prompt. Picking a model triggers
394-
// joinFreebuffQueue, which POSTs and transitions straight to 'active' (chat).
396+
// startFreebuffSession, which POSTs and transitions straight to 'active' (chat).
395397
const isLanding = session?.status === 'none'
396398
const streakQuery = useFreebuffStreakQuery({
397399
enabled: FREEBUFF_ENABLE_STREAK_IN_UI && isLanding,
@@ -799,7 +801,7 @@ export const WaitingRoomScreen: React.FC<WaitingRoomScreenProps> = ({
799801
</box>
800802

801803
{/* Reserve the ad banner slot before the async ad fetch resolves so the
802-
waiting-room content does not jump when the banner fills. On very
804+
landing content does not jump when the banner fills. On very
803805
short terminals the banner is dropped entirely to give the picker
804806
back its 5 rows. */}
805807
{showAds && (

cli/src/components/freebuff-model-selector.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
} from '@codebuff/common/constants/freebuff-models'
2323
import { getRateLimitsByModel } from '@codebuff/common/types/freebuff-session'
2424

25-
import { joinFreebuffQueue } from '../hooks/use-freebuff-session'
25+
import { startFreebuffSession } from '../hooks/use-freebuff-session'
2626
import { useNow } from '../hooks/use-now'
2727
import { useFreebuffLandingFocusStore } from '../state/freebuff-landing-focus-store'
2828
import { useFreebuffModelStore } from '../state/freebuff-model-store'
@@ -106,7 +106,7 @@ interface FreebuffModelSelectorProps {
106106
* this, the list scrolls (scrollbar shown, focused row kept in view);
107107
* otherwise the scrollbox shrinks to fit and no scrollbar appears. */
108108
maxHeight: number
109-
/** Notifies the parent whenever the picker expands/collapses. The waiting-room
109+
/** Notifies the parent whenever the picker expands/collapses. The landing
110110
* screen uses it to promote the wordmark to the full ASCII logo while the
111111
* picker is collapsed (the freed rows make room). */
112112
onExpandedChange?: (expanded: boolean) => void
@@ -118,7 +118,7 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
118118
}) => {
119119
const theme = useTheme()
120120
// contentMaxWidth (not terminalWidth) is the real budget — the parent
121-
// waiting-room screen wraps this picker in a `maxWidth: contentMaxWidth`
121+
// landing screen wraps this picker in a `maxWidth: contentMaxWidth`
122122
// box (capped at 80 cols), so a wide terminal doesn't actually let us
123123
// sprawl the buttons across it.
124124
const { contentMaxWidth } = useTerminalDimensions()
@@ -164,7 +164,7 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
164164
const [expanded, setExpanded] = useState(
165165
() => !canCollapse || !isLanding || selectedModel !== recommendedModel.id,
166166
)
167-
// Mirror the expanded state up to the waiting-room screen (collapsed → it
167+
// Mirror the expanded state up to the landing screen (collapsed → it
168168
// promotes the wordmark to the full ASCII logo). useLayoutEffect so the
169169
// parent's logo decision settles before paint, both on mount and on toggle.
170170
useLayoutEffect(() => {
@@ -463,7 +463,7 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
463463
if (modelId === committedModelId) return
464464
if (!isJoinable(modelId)) return
465465
setPending(modelId)
466-
joinFreebuffQueue(modelId).finally(() => setPending(null))
466+
startFreebuffSession(modelId).finally(() => setPending(null))
467467
},
468468
[pending, committedModelId, isJoinable],
469469
)

cli/src/components/freebuff-referral-banner.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { REFERRAL_CLI_DAILY_SESSION_BONUS_CAP } from '@codebuff/common/constants
1111
import { getReferralInfo } from '@codebuff/common/types/freebuff-session'
1212
import { pluralize } from '@codebuff/common/util/string'
1313

14-
import { joinFreebuffQueue } from '../hooks/use-freebuff-session'
14+
import { startFreebuffSession } from '../hooks/use-freebuff-session'
1515
import { useNow } from '../hooks/use-now'
1616
import { useFreebuffLandingFocusStore } from '../state/freebuff-landing-focus-store'
1717
import { useFreebuffSessionStore } from '../state/freebuff-session-store'
@@ -96,7 +96,7 @@ const CopyInviteLinkButton: React.FC<{
9696
}
9797

9898
/**
99-
* Advertises the "invite friends" reward on the waiting-room model screen. The
99+
* Advertises the "invite friends" reward on the landing model screen. The
100100
* reward — and the presentation — depends on the session's access tier:
101101
*
102102
* - LIMITED tier: referrals earn a daily free-session bonus (not GLM). One
@@ -132,7 +132,7 @@ export const FreebuffReferralBanner: React.FC = () => {
132132
const useGlm = useCallback(() => {
133133
setJoining((wasJoining) => {
134134
if (wasJoining) return wasJoining
135-
joinFreebuffQueue(FREEBUFF_GLM_V52_MODEL_ID).finally(() =>
135+
startFreebuffSession(FREEBUFF_GLM_V52_MODEL_ID).finally(() =>
136136
setJoining(false),
137137
)
138138
return true

cli/src/components/session-ended-banner.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
3333
}) => {
3434
const theme = useTheme()
3535
const [pendingAction, setPendingAction] = useState<
36-
'waiting-room' | 'same-chat' | null
36+
'landing' | 'same-chat' | null
3737
>(null)
3838

3939
// All premium models share one daily pool; the server replicates the same
@@ -62,10 +62,10 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
6262
const canRestart = !isStreaming && pendingAction === null
6363
const pickNewModel = useCallback(() => {
6464
if (!canRestart) return
65-
setPendingAction('waiting-room')
65+
setPendingAction('landing')
6666
// Drop back to the landing picker (status: 'none') so the user picks a
67-
// model and hits Enter again to commit, instead of being silently
68-
// re-queued. app.tsx swaps us into <WaitingRoomScreen> on the
67+
// model and hits Enter again to commit, instead of silently starting a
68+
// new session. app.tsx swaps us into <FreebuffLandingScreen> on the
6969
// transition, unmounting this banner — no need to clear the pending state on
7070
// success.
7171
returnToFreebuffLanding({ resetChat: true }).catch(() =>
@@ -153,7 +153,7 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
153153
style={{
154154
borderStyle: 'single',
155155
borderColor:
156-
pendingAction === 'waiting-room' ? theme.muted : theme.border,
156+
pendingAction === 'landing' ? theme.muted : theme.border,
157157
customBorderChars: BORDER_CHARS,
158158
paddingLeft: 1,
159159
paddingRight: 1,
@@ -163,12 +163,12 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
163163
<text
164164
style={{
165165
fg:
166-
pendingAction === 'waiting-room'
166+
pendingAction === 'landing'
167167
? theme.muted
168168
: theme.foreground,
169169
}}
170170
>
171-
{pendingAction === 'waiting-room' ? (
171+
{pendingAction === 'landing' ? (
172172
landingPendingLabel
173173
) : (
174174
<>

cli/src/hooks/helpers/send-message.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,9 @@ export const handleRunError = (params: {
552552
}
553553

554554
/**
555-
* Surface + recover from a waiting-room gate rejection. The server rejected
556-
* the request because our seat is no longer valid; update local state so the
557-
* UI reflects reality and we stop sending requests until we re-admit.
555+
* Surface + recover from a session gate rejection. The server rejected
556+
* the request because our session is no longer valid; update local state so
557+
* the UI reflects reality and we stop sending requests until we re-admit.
558558
*/
559559
function handleFreebuffGateError(
560560
kind: ReturnType<typeof getFreebuffGateErrorKind>,

cli/src/hooks/use-freebuff-session.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -341,18 +341,18 @@ export function refreshFreebuffLandingMetadata(): Promise<void> {
341341
}
342342

343343
/**
344-
* Join (or re-queue for) `model`. Dual-purpose:
345-
* - First join: called from the pre-chat landing picker. The session starts
344+
* Start a session on `model` (admitted immediately server-side). Dual-purpose:
345+
* - First start: called from the pre-chat landing picker. The session starts
346346
* at `none` (GET-only); this is the user's explicit commitment to enter.
347-
* - Switch: called when the user picks a different model from within the
348-
* waiting room. Server moves them to the back of the new model's queue.
347+
* - Switch: called when the user picks a different model from the landing
348+
* screen. The server admits them on the new model right away.
349349
*
350350
* If the server has already admitted them on a different model, it responds
351351
* with `model_locked`; the tick loop silently reverts the local selection to
352352
* the locked model so the active session stays intact. Users who really want
353353
* to switch can /end-session deliberately.
354354
*/
355-
export function joinFreebuffQueue(model: string): Promise<void> {
355+
export function startFreebuffSession(model: string): Promise<void> {
356356
if (!IS_FREEBUFF) return Promise.resolve()
357357
// This is the only explicit user-pick path (called from the picker on
358358
// click / Enter), so persistence belongs here — and ONLY here. Server-
@@ -395,7 +395,7 @@ export function markFreebuffSessionSuperseded(): void {
395395
* Used when the chat-completions gate rejects on country even though the
396396
* session-level country check did not catch the request first.
397397
* Transitioning the session state here unmounts the Chat surface in favor of
398-
* the waiting-room's country_blocked message, so the user can't keep typing
398+
* the landing screen's country_blocked message, so the user can't keep typing
399399
* and sending doomed requests. */
400400
export function markFreebuffSessionCountryBlocked(params: {
401401
countryCode: string
@@ -405,8 +405,8 @@ export function markFreebuffSessionCountryBlocked(params: {
405405
if (!IS_FREEBUFF) return
406406
controller?.abort()
407407
controller?.apply({ status: 'country_blocked', ...params })
408-
// Best-effort DELETE so we don't hold a waiting-room seat on a session the
409-
// server is already refusing to serve at chat time.
408+
// Best-effort DELETE so we don't hold a session row the server is already
409+
// refusing to serve at chat time.
410410
releaseFreebuffSlot().catch(() => {})
411411
}
412412

@@ -435,7 +435,7 @@ interface UseFreebuffSessionResult {
435435
/**
436436
* Manages the freebuff session lifecycle:
437437
* - GET on mount to probe state (no auto-join; the user picks a model in
438-
* the landing screen, which calls joinFreebuffQueue)
438+
* the landing screen, which calls startFreebuffSession)
439439
* - if the probe sees an existing seat, auto-takes-over when the prior
440440
* local owner process is gone; otherwise asks before POSTing to rotate
441441
* the instance id so any other CLI on the same account is superseded
@@ -525,7 +525,7 @@ export function useFreebuffSession(): UseFreebuffSessionResult {
525525
// need another POST.
526526
nextMethod = 'GET'
527527

528-
// Race recovery: user picked a different model in the waiting room at
528+
// Race recovery: user picked a different model on the landing screen at
529529
// the exact moment the server admitted them with the original model.
530530
// Silently revert the local selection and re-tick so the next call
531531
// (a GET) lands the actual active session. Users who really want to
@@ -579,15 +579,15 @@ export function useFreebuffSession(): UseFreebuffSessionResult {
579579

580580
// Bell on admission: the user committed to a model on the landing
581581
// screen (status 'none'), which POSTs and lands them straight on an
582-
// active session now that there's no waiting room.
582+
// active session (admission is immediate).
583583
if (previousStatus === 'none' && next.status === 'active') {
584584
playAdmissionSound()
585585
}
586586

587587
// active|ended → none means we've passed the server's hard cutoff.
588588
// Synthesize a no-instanceId ended state so the chat surface stays
589589
// mounted with the Enter-to-rejoin banner instead of looping back
590-
// through the waiting room. Carry forward whichever rate-limit
590+
// through the landing screen. Carry forward whichever rate-limit
591591
// snapshot we have — preferring the fresh `none` snapshot, falling
592592
// back to whatever was on the prior active/ended row — so the
593593
// banner's "N of M used today" line stays populated.
@@ -642,7 +642,7 @@ export function useFreebuffSession(): UseFreebuffSessionResult {
642642
// prevent. But the picker still needs live quota snapshots, so kick
643643
// off a fire-and-forget GET and extract only picker metadata from
644644
// the response, ignoring whatever status it claims. Polling resumes
645-
// when the user commits to a model via joinFreebuffQueue.
645+
// when the user commits to a model via startFreebuffSession.
646646
const landingSession = toLandingSession(
647647
useFreebuffSessionStore.getState().session,
648648
)

0 commit comments

Comments
 (0)