@@ -186,51 +186,76 @@ describe('FreebuffModelSelector tier layout', () => {
186186 expect ( frame ) . not . toContain ( '› MiniMax M3' )
187187 } )
188188
189- test ( 'shows the switch-to-Flash nudge only on the row the user is on' , async ( ) => {
189+ /**
190+ * ARMED, NOT DELETED. The catalog carries NO supersedes notice as of
191+ * 2026-08-21: V4 Pro held the last one ("V4 Flash is what we recommend") and
192+ * it was removed when Pro moved to a flat-priced lane and Flash became the
193+ * row that sleeps at peak — pointing Pro at Flash now steers users to a model
194+ * that is closed for ten hours precisely when Pro is their best option.
195+ *
196+ * The RULE this guards — only the selected row nags, so the list does not
197+ * repeat one notice on every row it applies to — is UI logic that outlives
198+ * any particular pair of models, so it runs again automatically the next time
199+ * a supersedes notice exists rather than being re-derived from a regression.
200+ */
201+ const allModelIds = FREEBUFF_MODELS . map ( ( m ) => m . id )
202+ const supersededModelId = allModelIds . find ( ( id ) =>
203+ getFreebuffModelSupersededBy ( id , allModelIds ) ,
204+ )
205+ test . if ( Boolean ( supersededModelId ) ) (
206+ 'shows the supersedes nudge only on the row the user is on' ,
207+ async ( ) => {
208+ useFreebuffSessionStore . getState ( ) . setSession ( {
209+ status : 'none' ,
210+ accessTier : 'full' ,
211+ } )
212+ // Assert against the real copy rather than a hardcoded fragment, so
213+ // rewording the notice doesn't fail this test for the wrong reason. It
214+ // must still render on ONE line — the width math reserves its length.
215+ const superseded = getFreebuffModelSupersededBy (
216+ supersededModelId ! ,
217+ allModelIds ,
218+ ) !
219+ const notice = superseded . notice
220+ const occurrences = ( frame : string ) => frame . split ( notice ) . length - 1
221+
222+ // On a superseded model: the nudge appears, once, on that model's card.
223+ useFreebuffModelStore . getState ( ) . setSelectedModel ( supersededModelId ! )
224+ const onSuperseded = ( await renderSelector ( ) ) . captureCharFrame ( )
225+ expect ( occurrences ( onSuperseded ) ) . toBe ( 1 )
226+
227+ // On a row that is NOT superseded, that notice stays quiet — otherwise
228+ // the list would repeat it on every row it applies to.
229+ const otherId = allModelIds . find ( ( id ) => id !== supersededModelId ) !
230+ useFreebuffModelStore . getState ( ) . setSelectedModel ( otherId )
231+ const onOther = ( await renderSelector ( ) ) . captureCharFrame ( )
232+ expect ( occurrences ( onOther ) ) . toBe ( 0 )
233+
234+ // And on the replacement itself: no nudge at all.
235+ useFreebuffModelStore
236+ . getState ( )
237+ . setSelectedModel ( superseded . modelId )
238+ const onCurrent = ( await renderSelector ( ) ) . captureCharFrame ( )
239+ expect ( occurrences ( onCurrent ) ) . toBe ( 0 )
240+ } ,
241+ )
242+
243+ test ( 'badges the new builds so a returning user notices they changed' , async ( ) => {
244+ // Independent of the supersedes machinery above, which is why it is its own
245+ // test now: `isNew` is a property of the row, and Flash still carries it.
190246 useFreebuffSessionStore . getState ( ) . setSession ( {
191247 status : 'none' ,
192248 accessTier : 'full' ,
193249 } )
194- // Assert against the real copy rather than a hardcoded fragment, so
195- // rewording the notice doesn't fail this test for the wrong reason. It must
196- // still render on ONE line — the width math reserves exactly its length.
197- // V4 Pro carries the only supersedes notice left: MiniMax M3's went with
198- // the model on 2026-08-20, and MiMo's went when Flash became premium.
199- const notice = getFreebuffModelSupersededBy (
200- FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID ,
201- FREEBUFF_MODELS . map ( ( m ) => m . id ) ,
202- ) ! . notice
203- const occurrences = ( frame : string ) => frame . split ( notice ) . length - 1
204-
205- // On a superseded model: the nudge appears, once, on that model's card.
206- useFreebuffModelStore
207- . getState ( )
208- . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID )
209- const onSuperseded = ( await renderSelector ( ) ) . captureCharFrame ( )
210- expect ( occurrences ( onSuperseded ) ) . toBe ( 1 )
211- // It names the dated build, which is what the row it steers to is labelled.
212- expect ( notice ) . toContain ( 'DeepSeek V4 Flash 07/31' )
213- // The new builds are badged so a returning user notices they changed.
214- expect ( onSuperseded ) . toContain ( 'NEW' )
215-
216- // MiMo is superseded too and is on screen here, but only the selected row
217- // nags — otherwise the list would repeat the same notice on every row it
218- // applies to. MiMo 2.5 is on screen and, when it is not the selection, its
219- // own notice stays quiet. (V4 Pro played this part until it was paused on
220- // 2026-08-18 and left the picker entirely.)
221- useFreebuffModelStore
222- . getState ( )
223- . setSelectedModel ( FREEBUFF_MIMO_V25_MODEL_ID )
224- const onUnsuperseded = ( await renderSelector ( ) ) . captureCharFrame ( )
225- expect ( onUnsuperseded ) . toContain ( 'MiMo 2.5' )
226-
227- // On the replacement itself: no nudge at all. (Picking the recommended
228- // model also collapses the picker to its hero card.)
250+ // Selected explicitly: `isNew` sits on the V4 Flash row, and the collapsed
251+ // view draws only the card the user is on — which is V4 Pro by default
252+ // since 2026-08-21, and carries no badge.
229253 useFreebuffModelStore
230254 . getState ( )
231255 . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID )
232- const onCurrent = ( await renderSelector ( ) ) . captureCharFrame ( )
233- expect ( occurrences ( onCurrent ) ) . toBe ( 0 )
256+ const frame = ( await renderSelector ( ) ) . captureCharFrame ( )
257+ expect ( frame ) . toContain ( 'DeepSeek V4 Flash 07/31' )
258+ expect ( frame ) . toContain ( 'NEW' )
234259 } )
235260
236261 test ( 'places the exhausted-quota recommendation beneath UNLIMITED' , async ( ) => {
@@ -258,17 +283,18 @@ describe('FreebuffModelSelector tier layout', () => {
258283 const frame = setup . captureCharFrame ( )
259284 const premiumHeaderIndex = frame . indexOf ( 'PREMIUM' )
260285 const unlimitedHeaderIndex = frame . indexOf ( 'UNLIMITED' )
261- const recommendedLabelIndex = frame . indexOf ( 'RECOMMENDED' )
262- // The unlimited recommendation is MiMo 2.5 since 2026-08-18 — Flash moved
263- // into the premium group and can no longer be what a spent user lands on.
264- const recommendedModelIndex = frame . indexOf (
265- 'MiMo 2.5' ,
266- recommendedLabelIndex ,
267- )
286+ // Located by the ROW rather than by a ' RECOMMENDED ' border title, which
287+ // was removed on 2026-08-21 — nothing in the picker is badged as a
288+ // recommendation any more. The property under test is unchanged: when the
289+ // premium pool is spent, the row the user is steered onto sits in the
290+ // UNLIMITED group rather than above the list.
291+ //
292+ // MiMo 2.5 is that row since 2026-08-18 — Flash moved into the premium
293+ // group and can no longer be what a spent user lands on.
294+ const heroModelIndex = frame . indexOf ( 'MiMo 2.5' , unlimitedHeaderIndex )
268295
269296 expect ( unlimitedHeaderIndex ) . toBeGreaterThan ( premiumHeaderIndex )
270- expect ( recommendedLabelIndex ) . toBeGreaterThan ( unlimitedHeaderIndex )
271- expect ( recommendedModelIndex ) . toBeGreaterThan ( recommendedLabelIndex )
297+ expect ( heroModelIndex ) . toBeGreaterThan ( unlimitedHeaderIndex )
272298 } )
273299
274300 test ( 'collapses to the unlimited hero when the premium default is spent' , async ( ) => {
@@ -282,9 +308,13 @@ describe('FreebuffModelSelector tier layout', () => {
282308 useFreebuffSessionStore . getState ( ) . setSession ( {
283309 status : 'none' ,
284310 accessTier : 'full' ,
311+ // Keyed on the CURRENT default rather than on a named model: the default
312+ // moved from Flash to V4 Pro on 2026-08-21, and this fixture has to
313+ // exhaust the pool of whichever row the picker will actually open on, or
314+ // the step-down under test never triggers.
285315 rateLimitsByModel : {
286- [ FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID ] : {
287- model : FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID ,
316+ [ DEFAULT_FREEBUFF_MODEL_ID ] : {
317+ model : DEFAULT_FREEBUFF_MODEL_ID ,
288318 limit : 6 ,
289319 period : 'pacific_day' ,
290320 resetTimeZone : 'America/Los_Angeles' ,
0 commit comments