@@ -772,7 +772,14 @@ const process: Record<string, unknown> & {
772772 } ,
773773
774774 nextTick ( callback : ( ...args : unknown [ ] ) => void , ...args : unknown [ ] ) : void {
775- if ( typeof queueMicrotask === "function" ) {
775+ // Route through bridge timer to avoid infinite microtask loops in V8's
776+ // perform_microtask_checkpoint() — TUI render cycles (Pi) use nextTick
777+ // in requestRender → doRender → requestRender loops
778+ if ( typeof _scheduleTimer !== "undefined" ) {
779+ _scheduleTimer
780+ . apply ( undefined , [ 0 ] , { result : { promise : true } } )
781+ . then ( ( ) => callback ( ...args ) ) ;
782+ } else if ( typeof queueMicrotask === "function" ) {
776783 queueMicrotask ( ( ) => callback ( ...args ) ) ;
777784 } else {
778785 Promise . resolve ( ) . then ( ( ) => callback ( ...args ) ) ;
@@ -1076,13 +1083,22 @@ function _checkTimerBudget(): void {
10761083 }
10771084}
10781085
1079- // queueMicrotask fallback
1086+ // queueMicrotask — route through bridge timer when available to prevent
1087+ // infinite microtask loops in V8's perform_microtask_checkpoint().
1088+ // TUI frameworks (Ink/React) schedule renders via queueMicrotask, which
1089+ // creates unbounded microtask chains that block the V8 event loop.
10801090const _queueMicrotask =
1081- typeof queueMicrotask === "function"
1082- ? queueMicrotask
1083- : function ( fn : ( ) => void ) : void {
1084- Promise . resolve ( ) . then ( fn ) ;
1085- } ;
1091+ typeof _scheduleTimer !== "undefined"
1092+ ? function ( fn : ( ) => void ) : void {
1093+ _scheduleTimer
1094+ . apply ( undefined , [ 0 ] , { result : { promise : true } } )
1095+ . then ( fn ) ;
1096+ }
1097+ : typeof queueMicrotask === "function"
1098+ ? queueMicrotask
1099+ : function ( fn : ( ) => void ) : void {
1100+ Promise . resolve ( ) . then ( fn ) ;
1101+ } ;
10861102
10871103/**
10881104 * Timer handle that mimics Node.js Timeout (ref/unref/Symbol.toPrimitive).
@@ -1125,11 +1141,9 @@ export function setTimeout(
11251141
11261142 const actualDelay = delay ?? 0 ;
11271143
1128- // Use host timer for actual delays if available and delay > 0
1129- if ( typeof _scheduleTimer !== "undefined" && actualDelay > 0 ) {
1130- // _scheduleTimer.apply() returns a Promise that resolves after the delay
1131- // Using { result: { promise: true } } tells the V8 runtime to wait for the
1132- // host Promise to resolve before resolving the apply() Promise
1144+ // Route ALL timers through bridge when available (including delay=0) to
1145+ // avoid infinite microtask loops in V8's perform_microtask_checkpoint()
1146+ if ( typeof _scheduleTimer !== "undefined" ) {
11331147 _scheduleTimer
11341148 . apply ( undefined , [ actualDelay ] , { result : { promise : true } } )
11351149 . then ( ( ) => {
@@ -1143,7 +1157,7 @@ export function setTimeout(
11431157 }
11441158 } ) ;
11451159 } else {
1146- // Use microtask for zero delay or when host timer is unavailable
1160+ // Use microtask only when host timer bridge is unavailable
11471161 _queueMicrotask ( ( ) => {
11481162 if ( _timers . has ( id ) ) {
11491163 _timers . delete ( id ) ;
@@ -1184,8 +1198,8 @@ export function setInterval(
11841198 const scheduleNext = ( ) => {
11851199 if ( ! _intervals . has ( id ) ) return ; // Interval was cleared
11861200
1187- if ( typeof _scheduleTimer !== "undefined" && actualDelay > 0 ) {
1188- // Use host timer for actual delays
1201+ if ( typeof _scheduleTimer !== "undefined" ) {
1202+ // Route through bridge timer to avoid microtask loops
11891203 _scheduleTimer
11901204 . apply ( undefined , [ actualDelay ] , { result : { promise : true } } )
11911205 . then ( ( ) => {
@@ -1200,7 +1214,7 @@ export function setInterval(
12001214 }
12011215 } ) ;
12021216 } else {
1203- // Use microtask for zero delay or when host timer unavailable
1217+ // Use microtask only when host timer bridge is unavailable
12041218 _queueMicrotask ( ( ) => {
12051219 if ( _intervals . has ( id ) ) {
12061220 try {
@@ -1336,10 +1350,9 @@ export function setupGlobals(): void {
13361350 g . setImmediate = setImmediate ;
13371351 g . clearImmediate = clearImmediate ;
13381352
1339- // queueMicrotask
1340- if ( typeof g . queueMicrotask === "undefined" ) {
1341- g . queueMicrotask = _queueMicrotask ;
1342- }
1353+ // queueMicrotask — always override to route through bridge timer when
1354+ // available, preventing infinite microtask loops from TUI render cycles
1355+ g . queueMicrotask = _queueMicrotask ;
13431356
13441357 // URL
13451358 if ( typeof g . URL === "undefined" ) {
0 commit comments