From f69f3456b4429d09a93d0fd1f786d2e0b2e019db Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Mon, 6 Jul 2026 18:01:27 -0700 Subject: [PATCH] Session View Animation Nucleic-Session: A8FFA1A6-443F-444A-A735-C53958D7E570 Co-authored-by: Nucleic --- .../Views/Transcript/MarkdownText.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) 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) } + } + } + } + } }