diff --git a/NucleicRemote/NucleicRemote/Views/Transcript/MarkdownText.swift b/NucleicRemote/NucleicRemote/Views/Transcript/MarkdownText.swift index 82a5457..4ea8584 100644 --- a/NucleicRemote/NucleicRemote/Views/Transcript/MarkdownText.swift +++ b/NucleicRemote/NucleicRemote/Views/Transcript/MarkdownText.swift @@ -258,4 +258,35 @@ struct MarkdownText: View { inlineCache.setObject(InlineBox(parsed), forKey: key) return parsed } + + // MARK: - Pre-warming + + /// Populate the block and inline caches for `markdown` ahead of layout. `AttributedString(markdown:)` + /// is the dominant per-row cost when a long transcript first lays out, and the eager `VStack` + /// runs it for every prose line synchronously — right as the session is pushed and the tab bar + /// is sliding away, which is what makes the load-in jitter. Calling this off the main thread + /// (see `TranscriptList`) does that parsing in the background so the first layout hits ready + /// results instead. + /// + /// Safe to call from any thread and redundantly: the caches are `NSCache` (thread-safe) and + /// keyed only by the source string (independent of `bodySize`), so a warm value equals what + /// the main thread would compute, and a repeat call is a cheap cache hit. Cooperatively + /// cancellable — a huge transcript's warm loop bails the moment its owning task is cancelled. + static func prewarm(_ markdown: String) { + for block in parse(markdown) { // also warms the block cache + if Task.isCancelled { return } + switch block { + case .code: + break // a code block renders via `Text(code)` — no inline parse to warm + case .text(let text): + for raw in text.components(separatedBy: "\n") { + if let content = classify(raw).inlineContent { _ = attributedInline(content) } + } + case .table(let rows): + for row in rows where !Task.isCancelled { + for cell in row { _ = attributedInline(cell) } + } + } + } + } }