import { act, render } from '@testing-library/react'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { createServer } from 'node:http'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { Profiler } from 'react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import type { HostApiContext } from '../../electron/api/context'; import { handleCodingConversationRoutes } from '../../electron/api/routes/coding-conversations'; import { CodingProjectService } from '../../electron/coding-projects/project-service'; import { createCodingProjectAgent } from '../../electron/coding-projects/project-config'; import { createCodingProjectStore, createLocalCodingProject, createMemoryCodingProjectStorage, } from '../../electron/coding-projects/project-store'; import { CodingConversationService } from '../../electron/coding-runtime/conversation-service'; import { InMemoryConversationRuntime } from '../../electron/coding-runtime/in-memory-conversation-runtime'; import type { CodingConversationPatchBatchEvent, CodingConversationSnapshotEvent, ConversationPatch, ConversationPatchEnvelope, } from '../../electron/coding-runtime/contracts'; import { CodingConversationTimeline } from '../../src/pages/Chat/CodingConversationTimeline'; import { codingConversationStore } from '../../src/stores/coding-conversations'; const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); function percentile95(values: number[]): number { const ordered = [...values].sort((left, right) => left - right); return ordered[Math.ceil(ordered.length * 0.95) - 1] ?? Number.POSITIVE_INFINITY; } function summarize(values: number[]): { samples: number; p50: number; p95: number; max: number } { const ordered = [...values].sort((left, right) => left - right); return { samples: ordered.length, p50: ordered[Math.ceil(ordered.length * 0.5) - 1] ?? Number.POSITIVE_INFINITY, p95: percentile95(ordered), max: ordered.at(-1) ?? Number.POSITIVE_INFINITY, }; } function pressurePatch(seq: number): ConversationPatch { if (seq === 1) { return { op: 'message.upsert', node: { kind: 'message', id: 'assistant-pressure', role: 'assistant', status: 'streaming', blocks: [ { kind: 'text', id: 'answer-pressure', text: 'Mixed answer', status: 'complete' }, { kind: 'thinking', id: 'thinking-pressure', text: '', status: 'streaming' }, ], }, }; } if (seq === 2) { return { op: 'tool.upsert', node: { kind: 'tool', id: 'tool-pressure', toolCallId: 'call-pressure', toolName: 'read', title: 'Pressure fixture tool output', inputText: 'fixture.txt', status: 'complete', output: [{ kind: 'text', id: 'tool-pressure-output', text: 'T'.repeat(4 * 1024), status: 'complete', }], }, }; } return { op: 'message.block-delta', messageId: 'assistant-pressure', blockId: 'thinking-pressure', delta: `${seq}:`.padEnd(1_100, 'x'), }; } describe('REN-008 coding timeline pressure', () => { it('batches 100 KB of mixed output and keeps Main-to-React p95 within 50 ms', async () => { const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-pressure-')); roots.push(root); const projectStore = createCodingProjectStore(createMemoryCodingProjectStorage(), { createId: () => 'project-pressure', now: () => '2026-08-24T00:00:00.000Z', }); await createLocalCodingProject({ projectPath: root, now: '2026-08-24T00:00:00.000Z', }, projectStore); await createCodingProjectAgent(root, { id: 'builder', avatarId: 'avatar-01', roleName: '实现者', name: 'Builder', model: { accountId: 'account-a', modelId: 'model-a', thinkingLevel: 'medium' }, modelResolution: 'resolved', responsibility: { mission: 'Implement', owns: [], boundaries: [], collaborators: [], principles: [], }, }, { now: '2026-08-24T00:00:00.000Z' }); const runtime = new InMemoryConversationRuntime(); const projects = new CodingProjectService(projectStore); const conversations = new CodingConversationService(projects, runtime, { deliveryBatchWindowMs: 24, }); const conversation = await conversations.createConversation({ agentId: 'builder', title: 'Pressure', }); let publish: ((event: ConversationPatchEnvelope) => void) | undefined; vi.spyOn(runtime, 'subscribe').mockImplementation((listener) => { publish = listener; return () => undefined; }); const context = { codingProducts: { projects, conversations, runtime }, } as unknown as HostApiContext; const server = createServer((request, response) => { const url = new URL(request.url ?? '/', 'http://127.0.0.1'); void handleCodingConversationRoutes(request, response, url, context); }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); const address = server.address(); if (!address || typeof address === 'string') throw new Error('Pressure server did not bind'); const controller = new AbortController(); const response = await fetch( `http://127.0.0.1:${address.port}/api/coding/events?conversationId=${conversation.id}`, { signal: controller.signal }, ); const reader = response.body?.getReader(); if (!reader) throw new Error('Pressure SSE response has no body'); const decoder = new TextDecoder(); const encoder = new TextEncoder(); let buffered = ''; let wireBytes = 0; let sseFrames = 0; const nextFrame = async (): Promise<{ event: string; data: unknown }> => { while (!buffered.includes('\n\n')) { const next = await reader.read(); if (next.done) throw new Error('Pressure SSE ended early'); buffered += decoder.decode(next.value, { stream: true }); } const boundary = buffered.indexOf('\n\n'); const frame = buffered.slice(0, boundary); buffered = buffered.slice(boundary + 2); wireBytes += encoder.encode(`${frame}\n\n`).byteLength; sseFrames += 1; const event = frame.split('\n').find((line) => line.startsWith('event: '))?.slice(7) ?? ''; const data = frame.split('\n').find((line) => line.startsWith('data: '))?.slice(6) ?? 'null'; return { event, data: JSON.parse(data) as unknown }; }; try { const initial = await nextFrame(); expect(initial.event).toBe('snapshot'); codingConversationStore.getState().applySnapshotEvent( initial.data as CodingConversationSnapshotEvent, ); let rendererTransactions = 0; const unsubscribe = codingConversationStore.subscribe(() => { rendererTransactions += 1; }); let reactCommits = 0; const view = render( { reactCommits += 1; }}> , ); const initialCommits = reactCommits; if (!publish) throw new Error('Pressure runtime subscriber was not installed'); const latencies: number[] = []; let runtimePatchItems = 0; let patchBatches = 0; for (let burst = 0; burst < 20; burst += 1) { const startedAt = performance.now(); for (let offset = 0; offset < 5; offset += 1) { const seq = burst * 5 + offset + 1; runtimePatchItems += 1; publish({ conversationId: conversation.id, workerGeneration: 0, seq, at: Date.now(), patch: pressurePatch(seq), }); } const frame = await nextFrame(); expect(frame.event).toBe('patch-batch'); const batch = frame.data as CodingConversationPatchBatchEvent; patchBatches += 1; expect(batch.items).toHaveLength(5); await act(async () => { codingConversationStore.getState().applyPatchBatchEvent(batch); }); latencies.push(performance.now() - startedAt); } const measuredReactCommits = reactCommits - initialCommits; const mainToReactMs = summarize(latencies); const metrics = { runtimePatchItems, patchBatches, sseFrames, rendererTransactions, reactCommits: measuredReactCommits, wireBytes, mainToReactMs, mainToReactP95Ms: mainToReactMs.p95, }; console.info('REN-008 metrics', metrics); expect(metrics.runtimePatchItems).toBe(100); expect(metrics.wireBytes).toBeGreaterThan(100 * 1024); expect(metrics.patchBatches).toBeLessThan(metrics.runtimePatchItems); expect(metrics.rendererTransactions).toBe(metrics.patchBatches); expect(metrics.reactCommits).toBeLessThan(metrics.runtimePatchItems); expect(metrics.sseFrames).toBe(metrics.patchBatches + 1); expect(metrics.mainToReactP95Ms).toBeLessThanOrEqual(50); expect(codingConversationStore.getState() .entriesByConversationId[conversation.id]?.reducer.snapshot?.cursor.seq).toBe(100); view.unmount(); unsubscribe(); const reportPath = process.env.MAKELORE_PI_PERF_PRESSURE_FRAGMENT; if (reportPath) await writeFile(reportPath, `${JSON.stringify(metrics, null, 2)}\n`); } finally { controller.abort(); await reader.cancel().catch(() => undefined); server.closeAllConnections(); await new Promise((resolve) => server.close(() => resolve())); } }); });