diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 379a502a..4d011d77 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -24,7 +24,7 @@ import { reportRunEvent } from '../../services/metricsService'; import { useProjectStore } from '../../store/useProjectStore'; import { LibraryManagerModal } from '../simulator/LibraryManagerModal'; import { InstallLibrariesModal } from '../simulator/InstallLibrariesModal'; -import { parseCompileResult, isNoiseBuildLine, tailAfterStream } from '../../utils/compilationLogger'; +import { parseCompileResult, isNoiseBuildLine } from '../../utils/compilationLogger'; import type { CompilationLog, CompileTarget } from '../../utils/compilationLogger'; import { exportToWokwiZip } from '../../utils/wokwiZip'; import { importProjectFile, PROJECT_FILE_ACCEPT } from '../../utils/importProject'; @@ -580,14 +580,14 @@ export const EditorToolbar = ({ }, ); - // After the build settles, flush the stdout tail the polling loop never - // delivered, then append the structured analysis — parseCompileResult - // highlights FAILED blocks and tags compiler errors with type='error', - // which the console uses for colour + the auto-switch-to-errors filter. - // streamedLive tells it not to reprint the stdout shown above. - const tailLogs = tailAfterStream(result.stdout, lastStreamedLen, boardTarget); + // After the build settles, append the structured analysis on top of + // the live stream — parseCompileResult highlights FAILED blocks and + // tags compiler errors with type='error', which the console uses for + // colour + the auto-switch-to-errors filter. streamedLive tells it not + // to reprint the stdout the stream (final 'done' flush included) + // already showed. const resultLogs = parseCompileResult(result, boardLabel, boardTarget, lastStreamedLen > 0); - setCompileLogs((prev: CompilationLog[]) => [...prev, ...tailLogs, ...resultLogs]); + setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]); if (result.success) { const program = result.hex_content ?? result.binary_content ?? null; @@ -1080,9 +1080,8 @@ export const EditorToolbar = ({ { boardOptions: board.boardOptions, spiffsFiles: board.spiffsFiles, libraries: board.libraries?.length ? board.libraries : null, language: board.languageMode === 'espidf' ? 'espidf' : undefined }, ); - const tailLogs = tailAfterStream(result.stdout, lastStreamedLen, boardTarget); const resultLogs = parseCompileResult(result, label, boardTarget, lastStreamedLen > 0); - setCompileLogs((prev: CompilationLog[]) => [...prev, ...tailLogs, ...resultLogs]); + setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]); if (result.success) { const program = result.hex_content ?? result.binary_content ?? null; diff --git a/frontend/src/services/compilation.ts b/frontend/src/services/compilation.ts index 989d8daf..41331a6c 100644 --- a/frontend/src/services/compilation.ts +++ b/frontend/src/services/compilation.ts @@ -56,12 +56,15 @@ interface CompileStatusResponse { /** * Live progress callback — called on every poll while state ∈ {pending, - * running}. `stdout` is the full live cmake + ninja output captured so far - * (cap of ~256 KB on the server side, tail kept). Caller can compute a - * delta against the previous call if it wants to append-only render. + * running}, plus one final call with state 'done' carrying the complete + * buffer (the lines that landed between the last running-poll and job + * completion would otherwise never be delivered). `stdout` is the full live + * cmake + ninja output captured so far (cap of ~256 KB on the server side, + * tail kept). Caller can compute a delta against the previous call if it + * wants to append-only render. */ export type CompileProgress = (info: { - state: 'pending' | 'running'; + state: 'pending' | 'running' | 'done'; stdout: string; elapsedSeconds: number; }) => void; @@ -172,6 +175,15 @@ export async function compileCode( if (status.state === 'done' && status.result) { const elapsed = Math.round((Date.now() - startedAt) / 1000); console.log(`[compile] job ${jobId} done in ${elapsed}s`); + // Final flush: the buffer grew between the last running-poll and + // completion (esptool + binary-size lines usually live there). + if (onProgress) { + try { + onProgress({ state: 'done', stdout: status.stdout || '', elapsedSeconds: elapsed }); + } catch (err) { + console.warn('[compile] onProgress threw:', err); + } + } return status.result; } diff --git a/frontend/src/utils/compilationLogger.ts b/frontend/src/utils/compilationLogger.ts index 0712d593..6b169b07 100644 --- a/frontend/src/utils/compilationLogger.ts +++ b/frontend/src/utils/compilationLogger.ts @@ -46,28 +46,6 @@ export function isNoiseBuildLine(line: string): boolean { return NOISE_LINE_PATTERNS.some((re) => re.test(stripped)); } -/** Lines of result.stdout past `streamedLen` — the tail that lands between - * the last live-status poll and job completion, which the polling loop never - * delivers (esptool output + the binary-size summary usually live there). - * Skipped when the backend's live buffer hit its 256 KB tail-trim cap: past - * that point buffer offsets no longer line up with the full stdout, and a - * slightly short log beats a misaligned one. */ -export function tailAfterStream( - stdout: string | undefined, - streamedLen: number, - target?: CompileTarget, -): CompilationLog[] { - const BUFFER_CAP = 262_144; - if (!stdout || streamedLen <= 0 || streamedLen >= BUFFER_CAP) return []; - if (stdout.length <= streamedLen) return []; - const now = new Date(); - return stdout - .slice(streamedLen) - .split('\n') - .filter((s) => s.trim() && !isNoiseBuildLine(s)) - .map((message) => ({ timestamp: now, type: 'info' as const, message, target })); -} - export function parseCompileResult( result: CompileResult, board: string,