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;