Skip to content

Commit 538c2cf

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@609ea6ace699cd3fbf5beb7693e28c5c54ebd1a1
1 parent 72c272d commit 538c2cf

3 files changed

Lines changed: 85 additions & 52 deletions

File tree

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

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ const renderSelector = async (
109109
}
110110

111111
test.each([
112-
['2026-09-17T23:00:00Z', '2026-09-19T06:00:00Z', 10, 15, 'Off-peak 10/hr', 0],
113-
['2026-09-17T21:00:00Z', '2026-09-18T22:00:00Z', 15, 10, 'normally 15/hr', 0],
114-
['2026-09-17T23:00:00Z', '2026-09-19T06:00:00Z', 10, 15, 'Off-peak 10/hr', 10],
115-
['2026-09-17T21:00:00Z', '2026-09-18T22:00:00Z', 15, 10, 'normally 15/hr', 10],
116-
] as const)('the mounted CLI picker catches up after multi-day sleep from %s', async (issued, resumed, before, after, explanation, discount) => {
112+
['2026-09-17T23:00:00Z', '2026-09-19T06:00:00Z', 10, 15, 0],
113+
['2026-09-17T21:00:00Z', '2026-09-18T22:00:00Z', 15, 10, 0],
114+
['2026-09-17T23:00:00Z', '2026-09-19T06:00:00Z', 10, 15, 10],
115+
['2026-09-17T21:00:00Z', '2026-09-18T22:00:00Z', 15, 10, 10],
116+
] as const)('the mounted CLI picker catches up after multi-day sleep from %s', async (issued, resumed, before, after, discount) => {
117117
const now = Date.parse(issued)
118118
const clock = spyOn(Date, 'now').mockReturnValue(now)
119119
const realTimeout = globalThis.setTimeout
@@ -135,17 +135,19 @@ test.each([
135135
})
136136
useFreebuffModelStore.getState().setSelectedModel(id)
137137
const setup = await renderSelector(40, undefined, 100, now)
138-
expect(setup.captureCharFrame()).toContain(`│ ${before - discount} Freebucks/hr`)
139-
expect(setup.captureCharFrame()).toContain(before === 10 ? 'normally 15/hr' : 'Off-peak 10/hr')
138+
expect(setup.captureCharFrame()).toMatch(new RegExp(`│ +${before - discount} Freebucks/hr`))
139+
expect(setup.captureCharFrame()).not.toContain('Off-peak')
140+
expect(setup.captureCharFrame()).not.toContain('normally 15/hr')
140141
expect(setup.captureCharFrame()).not.toContain('Server fallback price notice')
141142
expect(wake).toBeDefined()
142143
flushSync(() => {
143144
clock.mockReturnValue(Date.parse(resumed))
144145
wake!()
145146
})
146147
await setup.renderOnce()
147-
expect(setup.captureCharFrame()).toContain(`│ ${after - discount} Freebucks/hr`)
148-
expect(setup.captureCharFrame()).toContain(explanation)
148+
expect(setup.captureCharFrame()).toMatch(new RegExp(`│ +${after - discount} Freebucks/hr`))
149+
expect(setup.captureCharFrame()).not.toContain('Off-peak')
150+
expect(setup.captureCharFrame()).not.toContain('normally 15/hr')
149151
} finally {
150152
cleanupRenderer?.()
151153
cleanupRenderer = undefined
@@ -154,6 +156,42 @@ test.each([
154156
}
155157
})
156158

159+
test.each([
160+
['priceNotices', 40],
161+
['priceNotices', 100],
162+
['peak', 40],
163+
['peak', 100],
164+
] as const)('legacy %s copy stays hidden at %s columns while Flash remains selectable', async (payload, width) => {
165+
const id = FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID
166+
const price = payload === 'peak' ? 25 : 10
167+
useFreebuffSessionStore.getState().setSession({
168+
status: 'none',
169+
accessTier: 'full',
170+
freebucks: {
171+
...freebucksFixture(25, { [id]: price }),
172+
priceNotices: { [id]: 'Off-peak pricing · 15 Freebucks/hour at peak' },
173+
...(payload === 'peak' ? {
174+
peak: {
175+
modelIds: [id],
176+
surcharge: 10,
177+
endsAt: new Date(FIXED_NOW_MS + 3_600_000).toISOString(),
178+
},
179+
} : {}),
180+
},
181+
})
182+
useFreebuffModelStore.getState().setSelectedModel(id)
183+
const requested: string[] = []
184+
const setup = await renderSelector(40, async (model) => { requested.push(model) }, width)
185+
const frame = setup.captureCharFrame()
186+
expect(frame).toContain('› DeepSeek V4.1 Flash')
187+
expect(frame).toContain('Smart &')
188+
expect(frame).toMatch(new RegExp(`│ +${price} Freebucks/hr`))
189+
expect(frame).not.toMatch(/Off-peak|Peak pricing|charges double|Freebucks\/hour at peak/)
190+
flushSync(() => setup.mockInput.pressEnter())
191+
await setup.renderOnce()
192+
expect(requested).toEqual([id])
193+
})
194+
157195
/**
158196
* LIMITED tier, which since 2026-08-31 is the only tier where the reward is a
159197
* MODEL you can select. At full access the reward is an extra premium session
@@ -928,7 +966,7 @@ describe('GLM selection uses the applicable meter', () => {
928966
)
929967

930968
test.each([true, false])(
931-
'an available first-tab discount shows only the discounted price and is named limited-time, available=%s',
969+
'first-tab discount availability changes the displayed price without extra copy, available=%s',
932970
async (available) => {
933971
useFreebuffSessionStore.getState().setSession({
934972
status: 'none',
@@ -950,14 +988,14 @@ describe('GLM selection uses the applicable meter', () => {
950988
expect(frame).toContain('5 Freebucks/hr')
951989
expect(frame).not.toContain('15 5 Freebucks/hr')
952990
expect(frame).not.toContain('15 Freebucks/hr')
953-
expect(frame).toContain('Limited-time first-tab discount')
954991
} else {
955992
// In use elsewhere: the full price is the price, nothing struck, and
956993
// nothing advertised.
957994
expect(frame).toContain('15 Freebucks/hr')
958995
expect(frame).not.toContain('15 15 Freebucks/hr')
959-
expect(frame).not.toContain('first-tab discount')
960996
}
997+
expect(frame).not.toContain('first-tab discount')
998+
expect(frame).not.toContain('Prices shown include the discount')
961999
},
9621000
)
9631001

@@ -1142,6 +1180,34 @@ describe('a row the balance cannot cover', () => {
11421180
}
11431181
})
11441182

1183+
test('a pending paywall clears without discount prose when the balance refresh makes the row affordable', async () => {
1184+
const { setup, requested } = await renderUnaffordableLuna()
1185+
flushSync(() => setup.mockInput.pressEnter())
1186+
await setup.renderOnce()
1187+
expect(setup.captureCharFrame()).toContain(`Not enough ${FREEBUCKS_LABEL}`)
1188+
expect(requested).toEqual([])
1189+
1190+
flushSync(() => useFreebuffSessionStore.getState().setSession({
1191+
status: 'none',
1192+
accessTier: 'full',
1193+
freebucks: applyFirstTabDiscount(freebucksFixture(25, {
1194+
[FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: 20,
1195+
[FREEBUFF_MIMO_V25_MODEL_ID]: 10,
1196+
}), { amount: 10, available: true }),
1197+
}))
1198+
await setup.renderOnce()
1199+
const frame = setup.captureCharFrame()
1200+
expect(frame).toContain('› GPT-5.6 Luna')
1201+
expect(frame).toMatch(/ +10 Freebucks\/hr/)
1202+
expect(frame).not.toContain(`Not enough ${FREEBUCKS_LABEL}`)
1203+
expect(frame).not.toMatch(/first-tab discount|Prices shown include the discount/)
1204+
expect(requested).toEqual([])
1205+
1206+
flushSync(() => setup.mockInput.pressEnter())
1207+
await setup.renderOnce()
1208+
expect(requested).toEqual([FREEBUFF_GPT_5_6_LUNA_MODEL_ID])
1209+
})
1210+
11451211
test('a row closed for the hour stays inert — no wall to raise', async () => {
11461212
useFreebuffSessionStore.getState().setSession({
11471213
status: 'none',

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

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
import {
2-
freebucksPeakCopy,
3-
isFreebucksPeakModel,
4-
} from '@codebuff/common/util/freebuff-peak-price'
1+
import { isFreebucksPeakModel } from '@codebuff/common/util/freebuff-peak-price'
52
import { watchFreebucksPriceChanges } from '@codebuff/common/util/freebuff-price-changes'
6-
import { freebucksOffPeakCopy } from '@codebuff/common/util/freebuff-off-peak-price'
7-
import {
8-
firstTabDiscountCopy,
9-
firstTabListPriceFor,
10-
} from '@codebuff/common/util/freebuff-first-tab-discount'
3+
import { firstTabListPriceFor } from '@codebuff/common/util/freebuff-first-tab-discount'
114
import { TextAttributes } from '@opentui/core'
125
import { useKeyboard } from '@opentui/react'
136
import React, {
@@ -38,6 +31,7 @@ const FREEBUCKS_PLANS_URL = 'https://freebuff.com/plans'
3831

3932
import { FreebuffReferralBanner } from './freebuff-referral-banner'
4033
import {
34+
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
4135
FREEBUFF_REWARD_MODEL_ID,
4236
getFreebuffDeploymentAvailabilityLabel,
4337
getFreebuffModelUnavailableLabel,
@@ -356,6 +350,7 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
356350
)
357351
const taglineFor = useCallback(
358352
(model: FreebuffModelOption) =>
353+
model.id === FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID ||
359354
isFreebucksPeakModel(freebucks, model.id) || freebucks?.offPeak?.[model.id]
360355
? model.tagline
361356
: (freebucks?.priceNotices?.[model.id] ?? model.tagline),
@@ -450,41 +445,18 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
450445
// the balance cannot cover it — the same signal the dimmed price carries
451446
// on the other two surfaces.
452447
const rowPrice = freebucksPriceFor(freebucks, model.id)
453-
const offPeakCopy = freebucksOffPeakCopy(freebucks, model.id, { now: nowMs ?? Date.now() })
454448
if (rowPrice !== undefined) {
455449
// Only the price that will be charged. The regular price is NOT
456450
// shown beside it: many terminals ignore the strikethrough attribute,
457451
// and "10 0 Freebucks/hr" then reads as two prices. A row the
458-
// first-tab offer moved is drawn in the accent colour instead, next to
459-
// the "Limited-time first-tab discount" chip that says why.
452+
// first-tab offer moved is drawn in the accent colour instead.
460453
details.push({
461454
text: freebucksPriceLabel(rowPrice),
462455
warn: (freebucks?.balance ?? 0) < rowPrice,
463456
highlight: firstTabListPriceFor(freebucks, model.id) !== undefined,
464457
})
465-
if (offPeakCopy) details.push({ text: offPeakCopy.detail, warn: false })
466-
if (freebucks?.firstTabDiscount?.available) {
467-
// A promotion, not a price: named as one so nobody plans around a
468-
// row that will one day cost its regular price again.
469-
details.push({ text: 'Limited-time first-tab discount', warn: false })
470-
}
471458
}
472459
if (model.warning) details.push({ text: model.warning, warn: true })
473-
// PEAK PRICING as its own detail chip, in the reader's zone. Line 1
474-
// keeps the row's tagline (the server's prose notice is the same fact
475-
// and is dropped for a peaked row, see taglineFor); the price above
476-
// already moved, and this is the why and the when.
477-
if (freebucks?.peak && isFreebucksPeakModel(freebucks, model.id)) {
478-
const base = (rowPrice ?? 0) - freebucks.peak.surcharge
479-
details.push({
480-
text: freebucksPeakCopy({
481-
peak: freebucks.peak,
482-
basePrice: base,
483-
now,
484-
}).tooltip,
485-
warn: true,
486-
})
487-
}
488460
if (model.availability === 'deployment_hours') {
489461
// Carries both the in-hours and out-of-hours signal, so a row with
490462
// hours never also needs the closed note below.
@@ -520,7 +492,6 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
520492
[
521493
deploymentAvailabilityLabel,
522494
now,
523-
nowMs,
524495
premiumSectionQuotas,
525496
meterFor,
526497
freebucks,
@@ -690,7 +661,7 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
690661
intent.price,
691662
)}. Enter to confirm.`
692663
}
693-
return freebucks ? firstTabDiscountCopy(freebucks) : undefined
664+
return undefined
694665
},
695666
[pendingAsk, rowIntent, freebucks, activeSessionModel, upgradeOfferFor],
696667
)

common/src/util/freebuff-off-peak-price.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { discountedSessionPrice } from './freebuff-first-tab-discount'
77
import { offPeakPriceAt } from './freebuff-price-changes'
88

99
/** Presentation only: prices and schedules come from the server, never the
10-
* private rate card (this helper also ships in the public CLI). */
10+
* private rate card. */
1111
export function freebucksOffPeakCopy(
1212
info:
1313
| Pick<FreebuffFreebucksInfo, 'prices' | 'offPeak' | 'firstTabDiscount'>
@@ -39,10 +39,6 @@ export function freebucksOffPeakCopy(
3939
return {
4040
active,
4141
badge: 'Off-peak',
42-
// Terminals have no hover: show a concise explanation inline.
43-
detail: active
44-
? `Off-peak · normally ${offer.regularPrice}/hr · until ${fmt.format(end)} ${endZone}`
45-
: `Off-peak ${offer.price}/hr · ${hours}`,
4642
tooltip: `Off-peak: ${offer.price} Freebucks/hour, daily ${hours}.`,
4743
}
4844
}

0 commit comments

Comments
 (0)