fix(ai-chat): cache markdown parse, equatable code blocks, native scrollPosition (#1239)#1244
Merged
Conversation
…Capabilities collision
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1239.
Symptoms
Root cause
Three compounding bugs in
MarkdownView.swift+AIChatPanelView.swift:MarkdownBlock.id = UUID()(andMarkdownListItem.id = UUID()). Every parse generated fresh UUIDs, soForEachthought every block was new. SwiftUI tore down + rebuilt all child views on every re-render — includingAIChatCodeBlockView, which instantiates a fullCodeEditSourceEditor(tree-sitter, theme, layout) per block.MarkdownView.bodyre-parsed every evaluation.MarkdownBlockParser.parse(source)ran inline as aletinbody. Combined with bug 1, every scroll frame and every streaming chunk re-parsed every visible message and recreated every code block.Color.clearat the end of theLazyVStacktoggledisUserScrolledUpviaonAppear/onDisappear. When the heavy re-render churn (from bugs 1+2) caused cells to flicker out of materialization, the chevron overlaid an apparently-empty chat. The user pressing the chevron forced a layout pass that re-materialized cells.For a 5000-character response with 3 code blocks, each scroll frame was re-parsing ~100 markdown blocks and recreating 3 tree-sitter editors. That's the 100% CPU.
Fix
Four changes, no quick wins, native macOS APIs throughout:
MarkdownView.swiftMarkdownBlock.idandMarkdownListItem.idswitched fromUUID()to deterministicIntindexes (top-level position, item-index within list). SwiftUI now preserves view identity across re-parses.MarkdownDocumentCachestored as@StateperMarkdownViewinstance. Identical source returns cached blocks, no re-parse.MarkdownInline.parsebacked by anNSCache<NSString, NSAttributedString>(bounded at 4000 entries). Repeated inline parses (table cells, list items) are now O(1) lookups.MarkdownBlock,MarkdownBlock.Kind,MarkdownListItem,MarkdownTableAlignmentall conform.MarkdownBlockViewadoptsEquatable+.equatable()so SwiftUI skips re-render when the block is unchanged.AIChatCodeBlockView.swiftEquatableconformance keyed on(code, language). TheSourceEditoris no longer recreated when only ancestor state changes.AIChatPanelView.swiftColor.clearbottom anchor +onAppear/onDisappeartoggle with the native macOS 14+.scrollPosition(id:anchor: .bottom)API.bottomVisibleMessageID(driven by SwiftUI) +pinnedToBottom(derived: true when the bottommost visible message is the last message). The chevron-down button shows only whenpinnedToBottomis false.bottomVisibleMessageID = lastMessageIDinstead of usingScrollViewProxy.scrollTo("bottomAnchor").ScrollViewReaderremoved..scrollTargetLayout()added so SwiftUI can resolve target IDs.Why this is the right architecture
SourceEditorlifecycle is decoupled from parent re-renders viaEquatable. The expensive tree-sitter setup only runs when the underlying code actually changes.Test plan
Files changed
Views/AIChat/MarkdownView.swift(+~140 / -~100, structural rewrite)Views/AIChat/AIChatCodeBlockView.swift(+5)Views/AIChat/AIChatPanelView.swift(+~30 / -~30 messageList rewrite, scrollToBottom helper deleted)CHANGELOG.md(+1)