From 14af2a100ac2862b26b07b57bd99070863b0e6fa Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:16:32 +0530 Subject: [PATCH] build: Bump Android Gradle Plugin to 8.9.1 fix(chat): Improve Markdown formatter state management - Explicitly clear the streaming formatter when switching conversations to prevent state leakage. - Implement defensive checks to ensure the formatter is always associated with the correct message, resetting it if a mismatch is detected. - Refine the formatter seeding logic to only use existing content in resume scenarios, preventing content duplication. - Add detailed logging to the chat provider for better debugging of streaming and formatter behavior. --- android/settings.gradle.kts | 2 +- .../chat/providers/chat_providers.dart | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ab39a10..43394ed 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index 50db961..d578f45 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -153,6 +153,7 @@ class ChatMessagesNotifier extends Notifier> { // Cancel any existing message stream when switching conversations _cancelMessageStream(); + _clearStreamingFormatter(); // Explicitly clear formatter on conversation switch _stopRemoteTaskMonitor(); if (next != null) { @@ -268,15 +269,34 @@ class ChatMessagesNotifier extends Notifier> { } void _ensureFormatterForMessage(ChatMessage message) { + // If we're switching to a different message, clear the old formatter first + if (_markdownFormatter != null && _activeStreamingMessageId != message.id) { + DebugLogger.log( + 'Clearing formatter for message switch: $_activeStreamingMessageId -> ${message.id}', + scope: 'chat/providers', + ); + _clearStreamingFormatter(); + } + + // If formatter already exists for this message, reuse it if (_markdownFormatter != null && _activeStreamingMessageId == message.id) { return; } + // Create new formatter final formatter = MarkdownStreamFormatter(); + + // Only seed with existing content if this is a resume scenario + // For new messages (empty content), start fresh to avoid duplication final seed = _stripStreamingPlaceholders(message.content); - if (seed.isNotEmpty) { + if (seed.isNotEmpty && message.content.isNotEmpty) { + DebugLogger.log( + 'Seeding formatter with existing content (${seed.length} chars) for message ${message.id}', + scope: 'chat/providers', + ); formatter.seed(seed); } + _markdownFormatter = formatter; _activeStreamingMessageId = message.id; } @@ -548,10 +568,25 @@ class ChatMessagesNotifier extends Notifier> { } if (!lastMessage.isStreaming) { // Ignore late chunks when streaming already finished + DebugLogger.log( + 'Ignoring late chunk for finished message: ${lastMessage.id}', + scope: 'chat/providers', + ); return; } _ensureFormatterForMessage(lastMessage); + + // Defensive check: ensure the formatter is for the correct message + // This prevents cross-message pollution when messages change rapidly + if (_activeStreamingMessageId != lastMessage.id) { + DebugLogger.warning( + 'Formatter message ID mismatch: active=$_activeStreamingMessageId, last=${lastMessage.id}. Resetting formatter.', + ); + _clearStreamingFormatter(); + _ensureFormatterForMessage(lastMessage); + } + final formatter = _markdownFormatter!; final preview = formatter.ingest(content); @@ -572,7 +607,24 @@ class ChatMessagesNotifier extends Notifier> { return; } + // Log content replacement for debugging + DebugLogger.log( + 'Replacing message content: messageId=${lastMessage.id}, ' + 'oldLength=${lastMessage.content.length}, newLength=${content.length}', + scope: 'chat/providers', + ); + _ensureFormatterForMessage(lastMessage); + + // Defensive check: ensure the formatter is for the correct message + if (_activeStreamingMessageId != lastMessage.id) { + DebugLogger.warning( + 'Formatter message ID mismatch in replace: active=$_activeStreamingMessageId, last=${lastMessage.id}. Resetting formatter.', + ); + _clearStreamingFormatter(); + _ensureFormatterForMessage(lastMessage); + } + final formatter = _markdownFormatter!; final sanitized = formatter.replace(_stripStreamingPlaceholders(content));