From b23c07cf23a8fcbced961662db37e16c689e3a25 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 26 Mar 2026 16:33:48 +0200 Subject: [PATCH] perf(site): use lazy iteration in sliceAtGraphemeBoundary (#23671) Array.from(graphemeSegmenter.segment(text)) materializes the entire text into an array before iterating, even though the loop breaks early at the visible prefix length. During streaming at 60fps, this makes each frame O(full text) instead of O(prefix). Benchmark on 5000-char text with 200-char prefix: 22.6x faster (1.44ms to 0.06ms per call, saving 8.3% of the frame budget). The fallback codepoint path had the same issue with Array.from. --- .../components/AgentDetail/SmoothText.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/site/src/pages/AgentsPage/components/AgentDetail/SmoothText.ts b/site/src/pages/AgentsPage/components/AgentDetail/SmoothText.ts index 968a225de4..a3fc6ab39a 100644 --- a/site/src/pages/AgentsPage/components/AgentDetail/SmoothText.ts +++ b/site/src/pages/AgentsPage/components/AgentDetail/SmoothText.ts @@ -327,9 +327,12 @@ function sliceAtGraphemeBoundary( if (graphemeSegmenter) { let safeEnd = 0; - const segments = Array.from(graphemeSegmenter.segment(text)); - - for (const segment of segments) { + // Iterate the segmenter lazily instead of materializing + // with Array.from(). The early break makes this O(prefix) + // instead of O(full text), which matters at 60fps during + // streaming where the visible prefix is much shorter than + // the full accumulated text. + for (const segment of graphemeSegmenter.segment(text)) { const segmentEnd = segment.index + segment.segment.length; if (segmentEnd > maxCodeUnitLength) { break; @@ -342,9 +345,10 @@ function sliceAtGraphemeBoundary( // Fallback: iterate by codepoint to avoid splitting surrogate // pairs. This is less precise than grapheme segmentation but - // still safe for rendering. + // still safe for rendering. Iterating the string directly with + // for...of is lazy and avoids the O(n) Array.from() cost. let safeEnd = 0; - for (const codePoint of Array.from(text)) { + for (const codePoint of text) { const codePointEnd = safeEnd + codePoint.length; if (codePointEnd > maxCodeUnitLength) { break;