@@ -2,9 +2,16 @@ import { TextAttributes } from '@opentui/core'
22import { useKeyboard , useRenderer } from '@opentui/react'
33import React , { useCallback , useEffect , useRef , useState } from 'react'
44
5+ import {
6+ FREEBUCKS_LABEL ,
7+ formatFreebucks ,
8+ freebucksOf ,
9+ freebucksPriceLabel ,
10+ } from '../utils/freebucks'
511import { Button } from './button'
612import { ChoiceAdBanner , AD_CARD_HEIGHT } from './ad-banner'
713import { visibleWaitingRoomPlacementIds } from '@codebuff/common/ads/waiting-room-placements'
14+ import { FreebucksIntroCard } from './freebucks-intro-card'
815import { FreebuffModelSelector } from './freebuff-model-selector'
916import { ShimmerText } from './shimmer-text'
1017import {
@@ -674,6 +681,16 @@ export const FreebuffLandingScreen: React.FC<FreebuffLandingScreenProps> = ({
674681 gap : 0 ,
675682 } }
676683 >
684+ { /* The one-time Freebucks introduction, only where it fits:
685+ on a short terminal it would push the picker off the
686+ bottom, and an unseen card is shown on the next launch
687+ instead (it is marked seen only when it renders). */ }
688+ { terminalHeight >= 30 && (
689+ < FreebucksIntroCard
690+ metered = { freebucksOf ( session ) !== undefined }
691+ width = { Math . min ( contentMaxWidth , 72 ) }
692+ />
693+ ) }
677694 < LandingHeadingRow
678695 streakLine = { streakOnHeadingRow ? streakLine : null }
679696 marginBottom = { textMarginBottom }
@@ -795,26 +812,59 @@ export const FreebuffLandingScreen: React.FC<FreebuffLandingScreenProps> = ({
795812 { session ?. status === 'rate_limited' && (
796813 < >
797814 < text style = { { fg : theme . secondary , marginBottom : 1 } } >
798- ⚠ Session limit reached
799- </ text >
800- < text style = { { fg : theme . muted , wrapMode : 'word' } } >
801- You've used{ ' ' }
802- < span fg = { theme . foreground } >
803- { formatSessionUnits ( session . recentCount ) } of { session . limit }
804- </ span > { ' ' }
805- sessions{ ' ' }
806- { session . period === 'pacific_month'
807- ? 'this month'
808- : session . period === 'pacific_week'
809- ? 'this week'
810- : 'today' }
811- . Try
812- again in{ ' ' }
813- < span fg = { theme . foreground } >
814- { formatRetryAfter ( session . retryAfterMs ) }
815- </ span >
816- . Press Ctrl+C to exit.
815+ { session . freebucksShortfall
816+ ? `⚠ Not enough ${ FREEBUCKS_LABEL } `
817+ : '⚠ Session limit reached' }
817818 </ text >
819+ { /* THE METER'S REFUSAL, when the server sent one.
820+
821+ `freebucksShortfall` is what distinguishes "you are out of
822+ Freebucks" from "you have used your sessions", and the two
823+ want completely different sentences: the counts on this
824+ response are shaped like a session pool for older clients,
825+ so rendering them here would tell a metered user they had
826+ spent N of M sessions — a meter they are no longer on.
827+
828+ The monthly wall arrives in the SAME shape with no shortfall
829+ attached and `period: 'pacific_month'`, which is why the
830+ period copy below still has to be right: telling somebody to
831+ come back at midnight for an allowance that returns on the
832+ 1st sends them back to the same wall in the morning. */ }
833+ { session . freebucksShortfall ? (
834+ < text style = { { fg : theme . muted , wrapMode : 'word' } } >
835+ This model costs{ ' ' }
836+ < span fg = { theme . foreground } >
837+ { freebucksPriceLabel ( session . freebucksShortfall . price ) }
838+ </ span >
839+ , and you have{ ' ' }
840+ < span fg = { theme . foreground } >
841+ { formatFreebucks ( session . freebucksShortfall . balance ) }
842+ </ span > { ' ' }
843+ left. More in{ ' ' }
844+ < span fg = { theme . foreground } >
845+ { formatRetryAfter ( session . retryAfterMs ) }
846+ </ span >
847+ , or pick a cheaper model. Press Ctrl+C to exit.
848+ </ text >
849+ ) : (
850+ < text style = { { fg : theme . muted , wrapMode : 'word' } } >
851+ You've used{ ' ' }
852+ < span fg = { theme . foreground } >
853+ { formatSessionUnits ( session . recentCount ) } of { session . limit }
854+ </ span > { ' ' }
855+ { session . period === 'pacific_month' ? 'this month' : 'sessions' } { ' ' }
856+ { session . period === 'pacific_month'
857+ ? ''
858+ : session . period === 'pacific_week'
859+ ? 'this week'
860+ : 'today' }
861+ . Try again in{ ' ' }
862+ < span fg = { theme . foreground } >
863+ { formatRetryAfter ( session . retryAfterMs ) }
864+ </ span >
865+ . Press Ctrl+C to exit.
866+ </ text >
867+ ) }
818868 { /* The paywall half (2026-09-01): the server's structured hint
819869 when it sent one, the plans page otherwise — a quota wall is
820870 never a dead end. */ }
0 commit comments