@@ -160,20 +160,22 @@ describe('FreebuffModelSelector tier layout', () => {
160160 status : 'none' ,
161161 accessTier : 'full' ,
162162 } )
163- // The saved pick has to be something OTHER than the recommended hero, or
164- // the landing picker opens collapsed and there are no tier headers to
165- // order. The hero is GPT-5.6 Luna since 2026-08-24 -- Flash closes for the
166- // peak window and a default cannot be dark ten hours a day -- so FLASH is
167- // now the premium row that exercises "saved model stays focused" here.
163+ // The saved pick has to be a PREMIUM row that is NOT the recommended hero:
164+ // premium or the tier headers it is being ordered against don't apply to
165+ // it, non-hero or the landing picker opens collapsed and there are no tier
166+ // headers at all. The hero is GPT-5.6 Luna since 2026-08-24, which leaves
167+ // V4 Pro as the only row that is both. Flash filled this slot until
168+ // 2026-08-24, when it left FREEBUFF_PREMIUM_MODEL_IDS and moved down into
169+ // UNLIMITED -- below the header this asserts it sits above.
168170 useFreebuffModelStore
169171 . getState ( )
170- . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID )
172+ . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID )
171173
172174 const setup = await renderSelector ( )
173175 const frame = setup . captureCharFrame ( )
174176 const premiumHeaderIndex = frame . indexOf ( 'PREMIUM' )
175177 const recommendedModelIndex = frame . indexOf ( 'GPT-5.6 Luna' )
176- const selectedModelIndex = frame . indexOf ( 'DeepSeek V4 Flash ' )
178+ const selectedModelIndex = frame . indexOf ( 'DeepSeek V4 Pro ' )
177179 const unlimitedHeaderIndex = frame . indexOf ( 'UNLIMITED' )
178180
179181 expect ( premiumHeaderIndex ) . toBeGreaterThanOrEqual ( 0 )
@@ -183,8 +185,8 @@ describe('FreebuffModelSelector tier layout', () => {
183185 // 2026-08-20 and left the picker entirely.
184186 expect ( unlimitedHeaderIndex ) . toBeGreaterThan ( selectedModelIndex )
185187 // The cursor sits on the SAVED pick, not on the recommendation.
186- expect ( frame ) . toContain ( '› DeepSeek V4 Flash ' )
187- expect ( frame ) . not . toContain ( '› MiniMax M3 ' )
188+ expect ( frame ) . toContain ( '› DeepSeek V4 Pro ' )
189+ expect ( frame ) . not . toContain ( '› GPT-5.6 Luna ' )
188190 } )
189191
190192 /**
@@ -448,11 +450,20 @@ describe('FreebuffModelSelector tier layout', () => {
448450 } )
449451
450452 test ( 'sizes and centres a row around its per-row quota chip' , async ( ) => {
451- // The chip is drawn on a row whose pool is stricter than its section's
452- // (Luna's one-a-day ceiling), and it was missing from BOTH the centering
453- // math and the height estimate — visible only once a user had spent a Luna
454- // session, until the server began sending unused pool rows and it became
455- // every full-access picker.
453+ // The chip is drawn on a row that answers to a DIFFERENT pool than its
454+ // section header, and it was missing from BOTH the centering math and the
455+ // height estimate — visible only once a user had spent a Luna session,
456+ // until the server began sending unused pool rows and it became every
457+ // full-access picker.
458+ //
459+ // WHICH row wears it is arithmetic, not semantic: getFreebuffSectionQuotas
460+ // gives the header to the pool MOST rows share and breaks ties toward the
461+ // earlier row. With Flash in the section that was 2-1 for the shared
462+ // premium pool, so Luna's one-a-day ceiling wore the chip. Flash left that
463+ // pool on 2026-08-24, leaving Luna and V4 Pro tied 1-1, so the header now
464+ // speaks for Luna and it is V4 PRO that carries its own count. The
465+ // invariant under test — a second line the width and height math must
466+ // both know about — is unchanged; only the row it lands on moved.
456467 const resetAt = new Date ( FIXED_NOW_MS + 60_000 ) . toISOString ( )
457468 const pool = (
458469 model : string ,
@@ -473,13 +484,8 @@ describe('FreebuffModelSelector tier layout', () => {
473484 useFreebuffSessionStore . getState ( ) . setSession ( {
474485 status : 'none' ,
475486 accessTier : 'full' ,
487+ // Flash sends no pool row at all since 2026-08-24: it is unmetered.
476488 rateLimitsByModel : {
477- [ FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID ] : pool (
478- FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID ,
479- 'premium' ,
480- 'Premium' ,
481- 4 ,
482- ) ,
483489 [ FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID ] : pool (
484490 FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID ,
485491 'premium' ,
@@ -498,27 +504,37 @@ describe('FreebuffModelSelector tier layout', () => {
498504 . getState ( )
499505 // NOT the hero, so the picker opens expanded and the chip under test is
500506 // drawn at all. Luna took the hero slot on 2026-08-24; selecting it here
501- // collapses the list to a single row and the chip disappears.
507+ // collapses the list to a single row and the chip disappears. Flash also
508+ // supplies the warning-ONLY second line asserted below, which no premium
509+ // row has any more now that V4 Pro's carries the chip as well.
502510 . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID )
503511
504512 const frame = ( await renderSelector ( ) ) . captureCharFrame ( )
505513 // Gutters inside the card borders, which is what "centred" means here and
506514 // what a length the math didn't know about throws off. Asserted for the
507515 // ordinary warning line too, so this pins the invariant rather than the
508516 // one string that broke it.
509- const gutters = ( needle : string ) => {
510- const line = frame . split ( '\n' ) . find ( ( l ) => l . includes ( needle ) ) !
517+ const gutters = ( line : string ) => {
511518 const inner = line . slice ( line . indexOf ( '│' ) + 1 , line . lastIndexOf ( '│' ) )
512519 return [
513520 inner . length - inner . trimStart ( ) . length ,
514521 inner . length - inner . trimEnd ( ) . length ,
515522 ]
516523 }
517- for ( const needle of [
518- 'Luna: 0 of 2 used' ,
519- 'May use data for AI training' ,
520- ] ) {
521- const [ left , right ] = gutters ( needle )
524+ const lines = frame . split ( '\n' )
525+ // V4 Pro's second line carries the training warning AND the chip. Anchored
526+ // on the chip, so a chip that stops being drawn fails here rather than
527+ // quietly re-measuring some warning-only line instead.
528+ const chipLine = lines . find ( ( l ) => l . includes ( 'Premium: 0 of 4 used' ) )
529+ // Flash's carries the same warning with nothing after it — the shape the
530+ // width math already handled, which is the "ordinary warning line" above.
531+ const warningOnlyLine = lines . find (
532+ ( l ) => l . includes ( 'May use data for AI training' ) && ! l . includes ( 'used' ) ,
533+ )
534+ expect ( chipLine ) . toBeDefined ( )
535+ expect ( warningOnlyLine ) . toBeDefined ( )
536+ for ( const line of [ chipLine ! , warningOnlyLine ! ] ) {
537+ const [ left , right ] = gutters ( line )
522538 expect ( Math . abs ( left - right ) ) . toBeLessThanOrEqual ( 1 )
523539 }
524540 // A row the height estimate does not know has a second line costs the list
@@ -534,9 +550,11 @@ describe('FreebuffModelSelector tier layout', () => {
534550 status : 'none' ,
535551 accessTier : 'full' ,
536552 } )
537- // A premium row that isn't the hero, so the picker opens expanded and the
538- // PREMIUM header is actually drawn. Flash since 2026-08-24 -- Luna took the
539- // hero slot, so selecting Luna here would collapse the list.
553+ // A row that isn't the hero, so the picker opens expanded and the PREMIUM
554+ // header is actually drawn. Flash since 2026-08-24 -- Luna took the hero
555+ // slot, so selecting Luna here would collapse the list. The assertion is
556+ // the ABSENCE of numbers on that header, so the fact that Flash itself
557+ // stopped being premium that same day changes nothing here.
540558 useFreebuffModelStore
541559 . getState ( )
542560 . setSelectedModel ( FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID )
0 commit comments