@@ -1262,6 +1262,38 @@ async function fetchJsonSafe(url) {
12621262 }
12631263}
12641264
1265+ // ===== LRU Cache for Request Content =====
1266+ class RequestCache {
1267+ constructor ( maxSize = 20 ) {
1268+ this . maxSize = maxSize ;
1269+ this . cache = new Map ( ) ;
1270+ }
1271+
1272+ get ( key ) {
1273+ if ( ! this . cache . has ( key ) ) return null ;
1274+ const value = this . cache . get ( key ) ;
1275+ this . cache . delete ( key ) ;
1276+ this . cache . set ( key , value ) ; // Move to end (most recently used)
1277+ return value ;
1278+ }
1279+
1280+ set ( key , value ) {
1281+ if ( this . cache . has ( key ) ) {
1282+ this . cache . delete ( key ) ;
1283+ } else if ( this . cache . size >= this . maxSize ) {
1284+ const firstKey = this . cache . keys ( ) . next ( ) . value ;
1285+ this . cache . delete ( firstKey ) ;
1286+ }
1287+ this . cache . set ( key , value ) ;
1288+ }
1289+
1290+ has ( key ) {
1291+ return this . cache . has ( key ) ;
1292+ }
1293+ }
1294+
1295+ const requestContentCache = new RequestCache ( 20 ) ;
1296+
12651297function openRunViewer ( {
12661298 basePath,
12671299 vendor,
@@ -1355,7 +1387,7 @@ async function discoverTotalRequests(state) {
13551387 return maxFound ;
13561388}
13571389
1358- async function loadAndRenderRequest ( state ) {
1390+ async function loadAndRenderRequest ( state , prefetch = false ) {
13591391 const {
13601392 basePath,
13611393 vendor,
@@ -1367,15 +1399,45 @@ async function loadAndRenderRequest(state) {
13671399 } = state ;
13681400 const reqId = formatRequestId ( index ) ;
13691401 const runBase = buildRequestBasePath ( basePath , vendor , model , runId , reqId , strategy ) ;
1402+ const cacheKey = `${ runId } :${ index } ` ;
1403+
1404+ let content ;
1405+ const cached = requestContentCache . get ( cacheKey ) ;
13701406
1371- const [ reasoning , toolcall , strategyMd , gamestateMd , memoryMd , metadata ] = await Promise . all ( [
1372- fetchTextSafe ( `${ runBase } /reasoning.md` ) ,
1373- fetchJsonSafe ( `${ runBase } /tool_call.json` ) ,
1374- fetchTextSafe ( `${ runBase } /strategy.md` ) ,
1375- fetchTextSafe ( `${ runBase } /gamestate.md` ) ,
1376- fetchTextSafe ( `${ runBase } /memory.md` ) ,
1377- fetchJsonSafe ( `${ runBase } /metadata.json` )
1378- ] ) ;
1407+ if ( cached ) {
1408+ content = cached ;
1409+ } else {
1410+ const [ reasoning , toolcall , strategyMd , gamestateMd , memoryMd , metadata ] = await Promise . all ( [
1411+ fetchTextSafe ( `${ runBase } /reasoning.md` ) ,
1412+ fetchJsonSafe ( `${ runBase } /tool_call.json` ) ,
1413+ fetchTextSafe ( `${ runBase } /strategy.md` ) ,
1414+ fetchTextSafe ( `${ runBase } /gamestate.md` ) ,
1415+ fetchTextSafe ( `${ runBase } /memory.md` ) ,
1416+ fetchJsonSafe ( `${ runBase } /metadata.json` )
1417+ ] ) ;
1418+ content = {
1419+ reasoning,
1420+ toolcall,
1421+ strategyMd,
1422+ gamestateMd,
1423+ memoryMd,
1424+ metadata,
1425+ runBase
1426+ } ;
1427+ requestContentCache . set ( cacheKey , content ) ;
1428+ }
1429+
1430+ // If prefetching, just cache - don't render
1431+ if ( prefetch ) return ;
1432+
1433+ const {
1434+ reasoning,
1435+ toolcall,
1436+ strategyMd,
1437+ gamestateMd,
1438+ memoryMd,
1439+ metadata
1440+ } = content ;
13791441
13801442 // Discover total requests for this run if not cached
13811443 if ( ! state . totalRequests [ runId ] ) {
@@ -1416,26 +1478,30 @@ async function loadAndRenderRequest(state) {
14161478 overlay . querySelector ( '#run-title' ) . innerHTML = title ;
14171479
14181480 const imgEl = overlay . querySelector ( '#run-screenshot' ) ;
1419- // Try formats in order: webp -> png -> avif
1420- const formats = [ 'webp' , 'png' , 'avif' ] ;
1421- let formatIndex = 0 ;
14221481
1423- const tryNextFormat = ( ) => {
1424- if ( formatIndex < formats . length ) {
1425- imgEl . src = `${ runBase } /screenshot.${ formats [ formatIndex ] } ` ;
1426- formatIndex ++ ;
1482+ // Parallel format detection using HEAD requests
1483+ const formats = [ 'webp' , 'png' , 'avif' ] ;
1484+ const formatProbes = formats . map ( async ( format ) => {
1485+ const url = `${ content . runBase } /screenshot.${ format } ` ;
1486+ try {
1487+ const response = await fetch ( url , {
1488+ method : 'HEAD'
1489+ } ) ;
1490+ if ( response . ok ) return url ;
1491+ } catch {
1492+ /* format unavailable */
14271493 }
1428- } ;
1494+ return null ;
1495+ } ) ;
14291496
1430- imgEl . onerror = ( ) => {
1431- if ( formatIndex < formats . length ) {
1432- tryNextFormat ( ) ;
1497+ Promise . all ( formatProbes ) . then ( results => {
1498+ const availableUrl = results . find ( r => r !== null ) ;
1499+ if ( availableUrl ) {
1500+ imgEl . src = availableUrl ;
14331501 } else {
1434- imgEl . onerror = null ;
1502+ imgEl . alt = 'Screenshot not available' ;
14351503 }
1436- } ;
1437-
1438- tryNextFormat ( ) ;
1504+ } ) ;
14391505
14401506 overlay . querySelector ( '#run-strategy' ) . textContent = strategyMd || '(No strategy.md)' ;
14411507 overlay . querySelector ( '#run-gamestate' ) . textContent = gamestateMd || '(No gamestate.md)' ;
@@ -1481,6 +1547,31 @@ async function loadAndRenderRequest(state) {
14811547 toolCallDiv . textContent = `${ name } (${ argsString } )` ;
14821548 }
14831549
1550+ // Prefetch adjacent requests in background
1551+ prefetchAdjacentRequests ( state ) ;
1552+ }
1553+
1554+ function prefetchAdjacentRequests ( state ) {
1555+ const {
1556+ runId,
1557+ index
1558+ } = state ;
1559+
1560+ // Prefetch previous
1561+ if ( index > 1 && ! requestContentCache . has ( `${ runId } :${ index - 1 } ` ) ) {
1562+ loadAndRenderRequest ( {
1563+ ...state ,
1564+ index : index - 1
1565+ } , true ) . catch ( ( ) => { } ) ;
1566+ }
1567+
1568+ // Prefetch next
1569+ if ( ! requestContentCache . has ( `${ runId } :${ index + 1 } ` ) ) {
1570+ loadAndRenderRequest ( {
1571+ ...state ,
1572+ index : index + 1
1573+ } , true ) . catch ( ( ) => { } ) ;
1574+ }
14841575}
14851576
14861577async function navigateRun ( state , delta ) {
0 commit comments