diff --git a/services/clsi/app/js/TypstRunner.js b/services/clsi/app/js/TypstRunner.js index e87d99ce01..2193309621 100644 --- a/services/clsi/app/js/TypstRunner.js +++ b/services/clsi/app/js/TypstRunner.js @@ -55,6 +55,8 @@ const ProcessTable = {} // accumulator incomplete trailing line from the last data chunk // currentLines lines accumulated since the last compile-done event // pendingResolvers Array<{resolve, reject, timeoutHandle}> +// pendingResult compile result cached when typst finished before a +// resolver was registered (race-condition safety net) const WatchTable = {} // PIDs we have intentionally killed, so the close handler can distinguish @@ -141,6 +143,7 @@ async function _startWatcher(compileName, options) { accumulator: '', currentLines: [], pendingResolvers: [], + pendingResult: null, } WatchTable[compileName] = entry @@ -255,6 +258,15 @@ function _waitForNextCompile(compileName, timeout) { return reject(new Error('no typst watcher for ' + compileName)) } + // If typst finished a compile cycle before this resolver was registered + // (race: ResourceWriter wrote files → typst compiled → runTypst called), + // consume the cached result immediately instead of waiting for a timeout. + if (entry.pendingResult) { + const result = entry.pendingResult + entry.pendingResult = null + return resolve(result) + } + // Push synchronously inside the Promise constructor — before the first // await in the caller, so no data event can fire in the gap. const timeoutHandle = setTimeout(() => { @@ -272,7 +284,14 @@ function _waitForNextCompile(compileName, timeout) { function _resolveAllPending(compileName, result) { const entry = WatchTable[compileName] if (!entry) return - for (const { resolve, timeoutHandle } of entry.pendingResolvers.splice(0)) { + const resolvers = entry.pendingResolvers.splice(0) + if (resolvers.length === 0) { + // typst compiled before a resolver was registered — cache the result so + // the next _waitForNextCompile call can consume it immediately. + entry.pendingResult = result + return + } + for (const { resolve, timeoutHandle } of resolvers) { clearTimeout(timeoutHandle) resolve(result) }