From a996e5185089508ed80bb15f888a611d81f8b5f0 Mon Sep 17 00:00:00 2001 From: Lars Janssen <125481620+larsjtx@users.noreply.github.com> Date: Mon, 11 May 2026 08:46:30 +0200 Subject: [PATCH] Avoid stack overflow when rendering text with many attributed runs The reduce in TextBuilder built the result via Text("\(partialResult)\(text)"), wrapping every attributed run in a LocalizedStringKey-backed Text. At render time, SwiftUI's LocalizedStringKey.resolve walks the chain and recurses ~8 stack frames per nesting level, overflowing the main-thread stack for content that produces a few hundred runs (e.g. emoji-dense notes where each emoji forms its own run). Switch to Text + Text concatenation, which produces concat storage that resolves linearly. Fixes #56 --- Sources/Textual/Internal/TextFragment/TextBuilder.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/Textual/Internal/TextFragment/TextBuilder.swift b/Sources/Textual/Internal/TextFragment/TextBuilder.swift index 1910ba2d..87e0b4e8 100644 --- a/Sources/Textual/Internal/TextFragment/TextBuilder.swift +++ b/Sources/Textual/Internal/TextFragment/TextBuilder.swift @@ -96,9 +96,12 @@ extension Text { return text } - self = textValues.reduce(Text(verbatim: "")) { partialResult, text in - Text("\(partialResult)\(text)") - } + // Concatenate via `Text + Text` rather than `Text("\(partialResult)\(text)")`. + // The interpolation form produces a `LocalizedStringKey`-backed `Text` at every + // reduce step; resolving the resulting N-deep chain recurses ~8 stack frames per + // level during SwiftUI's render pass and overflows the main-thread stack for + // content that produces a few hundred attributed runs (e.g. emoji-dense notes). + self = textValues.reduce(Text(verbatim: ""), +) } private init(placeholderSize size: CGSize) {