Fix/chat perf (#502)

* perf(chat): batch public inserts, sort batch by ts; add per-sender + per-content token buckets; content-based near-dup suppression; reuse compiled regexes; single token scans per row; disable list animations during batches

* perf(chat): conditional animations via isBatchingPublic; late-arrival binary insert; flush public buffer on channel switch; prewarm formatting on flush

* perf(chat): batching, spam rate-limits, near-dup LRU, adaptive flush, faster trims, regex/detector reuse, conditional animations, late-insert, current-mode prewarm, Swift 6-safe timer/closures

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-08-24 11:11:49 +02:00
committed by GitHub
co-authored by jack
parent 0260798712
commit d2bfbbcfd4
3 changed files with 319 additions and 99 deletions
+11 -8
View File
@@ -84,6 +84,10 @@ struct ContentView: View {
ZStack {
// Base layer - Main public chat (always visible)
mainChatView
.onAppear { viewModel.currentColorScheme = colorScheme }
.onChange(of: colorScheme) { newValue in
viewModel.currentColorScheme = newValue
}
// Private chat slide-over
if viewModel.selectedPrivateChatPeer != nil {
@@ -310,9 +314,11 @@ struct ContentView: View {
} else {
// Regular messages with natural text wrapping
VStack(alignment: .leading, spacing: 0) {
// Precompute heavy token scans once per row
let cashuTokens = message.content.extractCashuTokens()
let lightningLinks = message.content.extractLightningLinks()
HStack(alignment: .top, spacing: 0) {
// Single text view for natural wrapping
let isLong = (message.content.count > 2000 || message.content.hasVeryLongToken(threshold: 512)) && message.content.extractCashuTokens().isEmpty
let isLong = (message.content.count > 2000 || message.content.hasVeryLongToken(threshold: 512)) && cashuTokens.isEmpty
let isExpanded = expandedMessageIDs.contains(message.id)
Text(viewModel.formatMessageAsText(message, colorScheme: colorScheme))
.fixedSize(horizontal: false, vertical: true)
@@ -328,9 +334,9 @@ struct ContentView: View {
}
// Expand/Collapse for very long messages
if (message.content.count > 2000 || message.content.hasVeryLongToken(threshold: 512)) && message.content.extractCashuTokens().isEmpty {
if (message.content.count > 2000 || message.content.hasVeryLongToken(threshold: 512)) && cashuTokens.isEmpty {
let isExpanded = expandedMessageIDs.contains(message.id)
Button(isExpanded ? "Show less" : "Show more") {
Button(isExpanded ? "show less" : "show more") {
if isExpanded { expandedMessageIDs.remove(message.id) }
else { expandedMessageIDs.insert(message.id) }
}
@@ -339,11 +345,7 @@ struct ContentView: View {
.padding(.top, 4)
}
// Link previews removed: URLs appear inline and are tappable
// Render payment chips (Lightning / Cashu) with rounded background
let lightningLinks = message.content.extractLightningLinks()
let cashuTokens = message.content.extractCashuTokens()
if !lightningLinks.isEmpty || !cashuTokens.isEmpty {
HStack(spacing: 8) {
ForEach(Array(lightningLinks.prefix(3)).indices, id: \.self) { i in
@@ -453,6 +455,7 @@ struct ContentView: View {
.padding(.vertical, 2)
}
}
.transaction { tx in if viewModel.isBatchingPublic { tx.disablesAnimations = true } }
.padding(.vertical, 4)
}
.background(backgroundColor)