@@ -32,7 +32,8 @@ const FRAME_MS = 1000 / 60;
3232interface SimResult {
3333 frames : number ;
3434 commits : number ;
35- markdownParses : number ;
35+ answerMarkdownParses : number ;
36+ reasoningMarkdownParses : number ;
3637 itemsIdentityBreaks : number ;
3738 bumpSkipViolations : number ;
3839 state : typeof initialState ;
@@ -49,15 +50,20 @@ function simulate(spec: UIPerfScenario, base?: typeof initialState): SimResult {
4950
5051 let frames = 0 ;
5152 let commits = 0 ;
52- let markdownParses = 0 ;
53+ let answerMarkdownParses = 0 ;
54+ let reasoningMarkdownParses = 0 ;
5355 let itemsIdentityBreaks = 0 ;
5456 let bumpSkipViolations = 0 ;
5557 let text = "" ;
56- let renderedLen = 0 ;
57- let lastParseAt = - Infinity ;
58+ let reasoning = "" ;
59+ let answerRenderedLen = 0 ;
60+ let reasoningRenderedLen = 0 ;
61+ let lastAnswerParseAt = - Infinity ;
62+ let lastReasoningParseAt = - Infinity ;
5863 let pendingChunks = 0 ;
5964 let index = 0 ;
6065 let firstBatch = true ;
66+ let reasoningFinalized = false ;
6167
6268 while ( index < chunks . length ) {
6369 frames += 1 ;
@@ -68,6 +74,7 @@ function simulate(spec: UIPerfScenario, base?: typeof initialState): SimResult {
6874 index += 1 ;
6975 pendingChunks -= 1 ;
7076 if ( chunk . kind === "text" ) text += chunk . delta ;
77+ else reasoning += chunk . delta ;
7178 batch . push ( { tabId : "a" , e : { kind : chunk . kind , text : chunk . delta } as WireEvent } ) ;
7279 }
7380 if ( batch . length === 0 ) continue ;
@@ -85,17 +92,31 @@ function simulate(spec: UIPerfScenario, base?: typeof initialState): SimResult {
8592 firstBatch = false ;
8693 }
8794 const nowMs = frames * FRAME_MS ;
88- if ( nowMs - lastParseAt >= streamingMarkdownCommitInterval ( text . length ) ) {
95+ if ( spec . reasoningVisible && reasoning . length > 0 && text . length > 0 && ! reasoningFinalized ) {
96+ reasoningMarkdownParses += 1 ;
97+ reasoningRenderedLen = reasoning . length ;
98+ reasoningFinalized = true ;
99+ }
100+ if ( spec . reasoningVisible && ! reasoningFinalized && nowMs - lastReasoningParseAt >= streamingMarkdownCommitInterval ( reasoning . length ) ) {
101+ const target = streamingCommitTarget ( reasoning ) ;
102+ if ( target . length > reasoningRenderedLen ) {
103+ reasoningMarkdownParses += 1 ;
104+ reasoningRenderedLen = target . length ;
105+ lastReasoningParseAt = nowMs ;
106+ }
107+ }
108+ if ( nowMs - lastAnswerParseAt >= streamingMarkdownCommitInterval ( text . length ) ) {
89109 const target = streamingCommitTarget ( text ) ;
90- if ( target . length > renderedLen ) {
91- markdownParses += 1 ;
92- renderedLen = target . length ;
93- lastParseAt = nowMs ;
110+ if ( target . length > answerRenderedLen ) {
111+ answerMarkdownParses += 1 ;
112+ answerRenderedLen = target . length ;
113+ lastAnswerParseAt = nowMs ;
94114 }
95115 }
96116 }
97- markdownParses += 1 ; // end-of-stream finalization parse
98- return { frames, commits, markdownParses, itemsIdentityBreaks, bumpSkipViolations, state } ;
117+ if ( text . length > answerRenderedLen ) answerMarkdownParses += 1 ;
118+ if ( spec . reasoningVisible && reasoning . length > reasoningRenderedLen ) reasoningMarkdownParses += 1 ;
119+ return { frames, commits, answerMarkdownParses, reasoningMarkdownParses, itemsIdentityBreaks, bumpSkipViolations, state } ;
99120}
100121
101122const byId = new Map ( UI_PERF_SCENARIOS . map ( ( s ) => [ s . id , s ] ) ) ;
@@ -111,8 +132,8 @@ const scenario = (id: string): UIPerfScenario => {
111132 const r = simulate ( spec ) ;
112133 ok ( r . commits <= r . frames + 1 , `01: one reducer pass per frame at most (${ r . commits } commits / ${ r . frames } frames)` ) ;
113134 ok (
114- r . markdownParses <= spec . paragraphs + 3 ,
115- `01: markdown parses bounded by blocks, not ticks (${ r . markdownParses } for ${ spec . paragraphs } paragraphs)` ,
135+ r . answerMarkdownParses <= spec . paragraphs + 3 ,
136+ `01: markdown parses bounded by blocks, not ticks (${ r . answerMarkdownParses } for ${ spec . paragraphs } paragraphs)` ,
116137 ) ;
117138 ok ( r . state . live !== undefined && r . state . live . text . length >= spec . textChars , "01: full answer reached the live stream" ) ;
118139}
@@ -123,7 +144,8 @@ const scenario = (id: string): UIPerfScenario => {
123144 const r = simulate ( spec ) ;
124145 const seconds = r . frames / 60 ;
125146 ok ( r . commits / seconds <= 61 , `02: state commits stay at or under display FPS (${ ( r . commits / seconds ) . toFixed ( 1 ) } /s)` ) ;
126- ok ( r . markdownParses <= spec . paragraphs + 3 , `02: reasoning stream causes no markdown parses (${ r . markdownParses } )` ) ;
147+ ok ( r . reasoningMarkdownParses <= 2 , `02: visible reasoning Markdown stays within its parse budget (${ r . reasoningMarkdownParses } )` ) ;
148+ ok ( r . answerMarkdownParses <= spec . paragraphs + 3 , `02: answer Markdown keeps its existing parse budget (${ r . answerMarkdownParses } )` ) ;
127149 ok ( r . state . live !== undefined && r . state . live . reasoning . length >= spec . reasoningChars , "02: full reasoning reached the live stream" ) ;
128150 eq ( r . state . live ?. reasoningComplete , true , "02: answer text after reasoning completed it" ) ;
129151}
@@ -134,12 +156,12 @@ const scenario = (id: string): UIPerfScenario => {
134156 const r = simulate ( spec ) ;
135157 ok ( r . commits <= r . frames + 1 , `03: commits bounded by frames (${ r . commits } /${ r . frames } )` ) ;
136158 ok (
137- r . markdownParses >= spec . codeFences ,
138- `03: open fences keep committing so streamed code stays highlighted (${ r . markdownParses } parses)` ,
159+ r . answerMarkdownParses >= spec . codeFences ,
160+ `03: open fences keep committing so streamed code stays highlighted (${ r . answerMarkdownParses } parses)` ,
139161 ) ;
140162 ok (
141- r . markdownParses <= Math . ceil ( r . frames / 3 ) + spec . paragraphs + spec . codeFences ,
142- `03: parse cadence capped by the 50ms tier even inside fences (${ r . markdownParses } parses / ${ r . frames } frames)` ,
163+ r . answerMarkdownParses <= Math . ceil ( r . frames / 3 ) + spec . paragraphs + spec . codeFences ,
164+ `03: parse cadence capped by the 50ms tier even inside fences (${ r . answerMarkdownParses } parses / ${ r . frames } frames)` ,
143165 ) ;
144166}
145167
@@ -168,6 +190,7 @@ const scenario = (id: string): UIPerfScenario => {
168190{
169191 const r = simulate ( scenario ( "UI-PERF-06" ) ) ;
170192 eq ( r . bumpSkipViolations , 0 , "06: background streaming never trips the whole-App bump — liveStore only" ) ;
193+ eq ( r . reasoningMarkdownParses , 0 , "06: background reasoning performs no Markdown parsing" ) ;
171194}
172195
173196process . stdout . write ( `\n${ passed } passed, ${ failed } failed\n` ) ;
0 commit comments