@@ -112,9 +112,14 @@ fof(b, conjecture, p).`,
112112 const pendingResolveRef = useRef ( null ) ;
113113 const pendingPromiseRef = useRef ( null ) ;
114114 const [ running , setRunning ] = useState ( false ) ;
115+ const [ hasOutput , setHasOutput ] = useState ( false ) ;
116+ const hasOutputRef = useRef ( false ) ;
117+ const [ runningDots , setRunningDots ] = useState ( 0 ) ;
115118 const outWrapRef = useRef ( null ) ;
116119 const outCodeRef = useRef ( null ) ;
117120 const latestOutRef = useRef ( 'Ready.' ) ;
121+ const flushTimerRef = useRef ( null ) ;
122+ const cancelRunRef = useRef ( null ) ;
118123 const runIdRef = useRef ( 0 ) ;
119124
120125 useEffect ( ( ) => {
@@ -125,7 +130,7 @@ fof(b, conjecture, p).`,
125130 useEffect ( ( ) => {
126131 if ( typeof window === 'undefined' ) return ;
127132 highlightNowOrWhenReady ( outCodeRef . current ) ;
128- } , [ out , outputLanguage , pendingPrompt , pendingInput , pendingInline ] ) ;
133+ } , [ out , outputLanguage ] ) ;
129134
130135 useEffect ( ( ) => {
131136 if ( ! outWrapRef . current ) return ;
@@ -137,8 +142,24 @@ fof(b, conjecture, p).`,
137142 outWrapRef . current ?. focus ( { preventScroll : true } ) ;
138143 } , [ pendingPrompt ] ) ;
139144
145+ useEffect ( ( ) => {
146+ if ( ! running ) {
147+ setRunningDots ( 0 ) ;
148+ return ;
149+ }
150+ const id = setInterval ( ( ) => {
151+ setRunningDots ( prev => ( prev + 1 ) % 3 ) ;
152+ } , 500 ) ;
153+ return ( ) => clearInterval ( id ) ;
154+ } , [ running ] ) ;
155+
140156 const visibleOut = ( ( ) => {
141- if ( pendingPrompt === null ) return out ;
157+ if ( pendingPrompt === null ) {
158+ if ( ! running || hasOutput ) return out ;
159+ const dots = '.' . repeat ( ( runningDots % 3 ) + 1 ) ;
160+ const sep = out ? '\n' : '' ;
161+ return out + sep + `Running${ dots } ` ;
162+ }
142163 const lastLineRaw = out . split ( '\n' ) . slice ( - 1 ) [ 0 ] || '' ;
143164 const lastLine = lastLineRaw . replace ( / \s + $ / , '' ) ;
144165 const forceInline = pendingMatchText && lastLine . endsWith ( pendingMatchText ) ;
@@ -149,27 +170,40 @@ fof(b, conjecture, p).`,
149170 return out + sep + ( pendingPrompt ?? '> ' ) + pendingInput ;
150171 } ) ( ) ;
151172
173+ const scheduleFlush = ( ) => {
174+ if ( flushTimerRef . current ) return ;
175+ flushTimerRef . current = setTimeout ( ( ) => {
176+ flushTimerRef . current = null ;
177+ setOut ( latestOutRef . current || '' ) ;
178+ } , 50 ) ;
179+ } ;
180+
181+ useEffect ( ( ) => ( ) => {
182+ if ( flushTimerRef . current ) {
183+ clearTimeout ( flushTimerRef . current ) ;
184+ }
185+ } , [ ] ) ;
186+
152187 // Append a line to the transcript (one big string for highlighting)
153188 const appendLine = ( text ) => {
154- setOut ( prev => {
155- const next = prev ? `${ prev } \n${ text } ` : text ;
156- latestOutRef . current = next ;
157- return next ;
158- } ) ;
189+ const prev = latestOutRef . current || '' ;
190+ latestOutRef . current = prev ? `${ prev } \n${ text } ` : text ;
191+ scheduleFlush ( ) ;
159192 } ;
160193
161194 const appendInline = ( text ) => {
162- setOut ( prev => {
163- const next = ( prev || '' ) + text ;
164- latestOutRef . current = next ;
165- return next ;
166- } ) ;
195+ latestOutRef . current = ( latestOutRef . current || '' ) + text ;
196+ scheduleFlush ( ) ;
167197 } ;
168198
169199 const handleRequestInput = ( promptText ) => {
170200 if ( pendingResolveRef . current ) {
171201 return pendingPromiseRef . current || new Promise ( ( ) => { } ) ;
172202 }
203+ if ( ! hasOutputRef . current ) {
204+ hasOutputRef . current = true ;
205+ setHasOutput ( true ) ;
206+ }
173207 const raw = String ( promptText ?? '' ) ;
174208 const trimmed = raw . replace ( / \s + $ / , '' ) ;
175209 const lastLineRaw = ( latestOutRef . current || '' ) . split ( '\n' ) . slice ( - 1 ) [ 0 ] || '' ;
@@ -240,12 +274,19 @@ fof(b, conjecture, p).`,
240274 async function onRun ( ) {
241275 const newRunId = runIdRef . current + 1 ;
242276 runIdRef . current = newRunId ;
277+ if ( cancelRunRef . current ) {
278+ cancelRunRef . current ( ) ;
279+ cancelRunRef . current = null ;
280+ }
243281 if ( pendingResolveRef . current ) {
244282 pendingResolveRef . current = null ;
245283 pendingPromiseRef . current = null ;
246284 }
247285 setRunning ( true ) ;
248- setOut ( 'Running…' ) ;
286+ setHasOutput ( false ) ;
287+ hasOutputRef . current = false ;
288+ latestOutRef . current = '' ;
289+ setOut ( '' ) ;
249290 setPendingPrompt ( null ) ;
250291 setPendingInput ( '' ) ;
251292 setPendingInline ( false ) ;
@@ -266,16 +307,31 @@ fof(b, conjecture, p).`,
266307 args,
267308 onStdout : ( msg ) => {
268309 if ( runIdRef . current !== newRunId ) return ;
269- String ( msg ?? '' ) . split ( '\n' ) . forEach ( appendLine ) ;
310+ const text = String ( msg ?? '' ) ;
311+ if ( text && ! hasOutputRef . current ) {
312+ hasOutputRef . current = true ;
313+ setHasOutput ( true ) ;
314+ }
315+ text . split ( '\n' ) . forEach ( appendLine ) ;
270316 } ,
271317 onStderr : ( msg ) => {
272318 if ( runIdRef . current !== newRunId ) return ;
273- String ( msg ?? '' ) . split ( '\n' ) . forEach ( line => appendLine ( `[err] ${ line } ` ) ) ;
319+ const text = String ( msg ?? '' ) ;
320+ if ( text && ! hasOutputRef . current ) {
321+ hasOutputRef . current = true ;
322+ setHasOutput ( true ) ;
323+ }
324+ text . split ( '\n' ) . forEach ( line => appendLine ( `[err] ${ line } ` ) ) ;
274325 } ,
275326 requestInput : ( promptText ) => {
276327 if ( runIdRef . current !== newRunId ) return new Promise ( ( ) => { } ) ;
277328 return handleRequestInput ( promptText ) ;
278329 } ,
330+ onReady : ( ready ) => {
331+ if ( ready && typeof ready . cancel === 'function' ) {
332+ cancelRunRef . current = ready . cancel ;
333+ }
334+ } ,
279335 } ) ;
280336 if ( runIdRef . current === newRunId ) {
281337 const exitCode = typeof code === 'number' ? code : 0 ;
@@ -300,6 +356,8 @@ fof(b, conjecture, p).`,
300356 setPendingSpacer ( '' ) ;
301357 setPendingMatchText ( '' ) ;
302358 setRunning ( false ) ;
359+ cancelRunRef . current = null ;
360+ scheduleFlush ( ) ;
303361 }
304362 }
305363 }
0 commit comments