Skip to content

Refactor: Move chat state ownership to ChatScreen#147

Open
g-k-s-03 wants to merge 1 commit intoAOSSIE-Org:mainfrom
g-k-s-03:fix/chat-state-ownership
Open

Refactor: Move chat state ownership to ChatScreen#147
g-k-s-03 wants to merge 1 commit intoAOSSIE-Org:mainfrom
g-k-s-03:fix/chat-state-ownership

Conversation

@g-k-s-03
Copy link
Copy Markdown
Contributor

@g-k-s-03 g-k-s-03 commented Feb 3, 2026

closes #122

What changed

This PR refactors chat architecture so that all chat-related state and logic
is owned by ChatScreen instead of HomeScreen.

Why

Previously, HomeScreen managed chat controllers and logic while not rendering
the chat UI. This violated feature ownership and made the code harder to
maintain and extend.

Key improvements

  • Chat state now lives entirely inside ChatScreen
  • HomeScreen is responsible only for navigation
  • Initial chat messages are passed via arguments
  • Prevents duplicated or lost messages on rebuild

Impact

  • Clear separation of concerns
  • Easier to test and scale chat features
  • Aligns with Flutter architectural best practices

Summary by CodeRabbit

  • New Features

    • User avatars in chat messages
    • Card-based messages with "View" to open item details
    • Typing indicator for in-progress AI responses
  • Improvements

    • More reliable speech-to-text with a stable listening dialog and better error handling
    • Startup/service readiness gating with message queuing and automatic flush
    • Stronger safeguards to prevent UI updates after screen disposal
    • Home screen now forwards initial chat content and centralizes chat state
  • UI

    • Chat layout reorganized into clearer header, list, and input sections

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Chat state and logic moved entirely into ChatScreen. Initialization now gates on service readiness, messages may be queued and flushed when ready, speech dialog lifecycle and mounted checks hardened, UI split into modular builders, and HomeScreen reduced to tab/navigation duties; pubspec SDK upper bound widened.

Changes

Cohort / File(s) Summary
Chat Screen State & Flow
lib/screens/chat/chat_screen.dart
Moved chat state into ChatScreen; added _servicesReady, _initialMessageConsumed, _pendingMessage, processing lock and queueing; moved init to didChangeDependencies/didUpdateWidget; added _flushPendingMessageIfPossible() and extra mounted checks.
Chat UI, STT & Rendering
lib/screens/chat/chat_screen.dart
Refactored UI into _buildHeader, _buildChatList, _buildInputBar, stabilized listening dialog with _isListeningDialogOpen, adjusted avatar/icon rendering and typing indicator in list.
Function Calls, Cards & History
lib/screens/chat/chat_screen.dart
Function-call handling updated: append placeholders, attach card items (task/ticket/meeting), use recent-window history retrieval, and ensure pending messages are flushed after processing.
Data Model & Safety Fixes
lib/screens/chat/chat_screen.dart
ChatMessage keeps avatarUrl; safer null-aware access for card data and Date/UUID checks; StringExtension.capitalize() guards empty strings.
Home Screen Simplification
lib/screens/home/home_screen.dart
Removed chat controllers/state from HomeScreen; tabs now built in build(); initState parses widget.arguments synchronously and forwards initial_message to ChatScreen via arguments/PageStorageKey('chat_screen').
Dependency Constraint
pubspec.yaml
Dart SDK upper bound widened from <=3.8.1 to <4.0.0.

Sequence Diagram(s)

sequenceDiagram
    participant UI as ChatScreen UI
    participant Manager as ChatManager
    participant Queue as PendingQueue
    participant Services as AI/Backend Services
    participant Renderer as UI Renderer

    UI->>Manager: init / didChangeDependencies
    Manager->>Services: initialize/check readiness
    Services-->>Manager: ready / not ready
    Manager->>Queue: create/retain pending slot

    Note over UI,Manager: initial_message or user input arrives

    alt Services not ready
        UI->>Manager: sendMessage(input)
        Manager->>Queue: enqueue input (_pendingMessage)
        Manager->>Renderer: show pending/typing state
    else Services ready
        UI->>Manager: sendMessage(input)
        Manager->>Services: process message
    end

    Services-->>Manager: response (message / function_call / error)
    Manager->>Renderer: render user/AI message, card, or error
    Manager->>Manager: _flushPendingMessageIfPossible()
    opt pending messages exist
        Manager->>Queue: dequeue next
        Manager->>Services: process dequeued message
        Services-->>Manager: response
        Manager->>Renderer: update UI
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Dart/Flutter

Poem

🐇 I queued the whispers, held them tight,
Waiting for services to wake and write.
Mic hums soft, dialogs calm and clear,
Cards hop out when answers appear.
Home stepped back — Chat leads the night.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: moving chat state ownership from HomeScreen to ChatScreen, which is the core refactoring objective.
Linked Issues check ✅ Passed The PR successfully implements all coding objectives from issue #122: chat state is moved to ChatScreen, HomeScreen reduced to navigation only, initial messages passed via arguments, and the architectural inconsistency is resolved.
Out of Scope Changes check ✅ Passed The Dart SDK constraint widening in pubspec.yaml (2.17.0 to <4.0.0) is a minor supporting change; all other modifications directly support the core refactoring objective without introducing unrelated functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.

Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@lib/screens/chat/chat_screen.dart`:
- Around line 1204-1206: The StringExtension.capitalize currently accesses
this[0] and will throw on an empty string; update the extension method
(StringExtension and its capitalize) to guard against empty strings by returning
the original string (or an empty string) when this.isEmpty before accessing
this[0] and substring(1), ensuring safe reuse elsewhere.
- Around line 643-666: Move the state flip for _isListening to occur before
calling showDialog so the dialog's .then() cleanup sees the correct state;
specifically, setState(() => _isListening = true) before invoking
showDialog(context: ..., builder: (_) => _buildListeningDialog()), then call
await _speech.listen(...) as before; ensure the .then() callback that stops
_speech (and sets _isListening = false) still checks _speech.isListening and
mounted so the engine won't be left running if the dialog is dismissed quickly.
🧹 Nitpick comments (3)
lib/screens/chat/chat_screen.dart (3)

172-176: Consider: Single pending message means intermediate inputs are overwritten.

When the user types while _isProcessing is true, only the latest message is queued. Rapid successive inputs before processing completes will lose earlier messages. This is likely acceptable for typical chat UX, but worth documenting.


350-363: Duplicated UUID validation and team member lookup logic.

The UUID regex pattern and team member lookup logic are repeated in _createTask, _createTicket, _queryTasks, and _queryTickets. Consider extracting to reusable helpers.

♻️ Proposed refactor: Extract helper methods
// Add these helper methods to _ChatScreenState:

static final _uuidPattern = RegExp(
  r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
  caseSensitive: false,
);

String? _resolveUserId(String? input) {
  if (input == null || input.isEmpty) return null;
  if (_uuidPattern.hasMatch(input)) return input;
  
  final match = _teamMembers.firstWhere(
    (m) => m['full_name'].toString().toLowerCase() == input.toLowerCase(),
    orElse: () => {},
  );
  return match.isNotEmpty ? match['id'] : null;
}

Then replace inline regex/lookup blocks with _resolveUserId(assignedTo).


1191-1201: avatarUrl field is declared but unused.

The avatarUrl field is added to ChatMessage but _ChatBubble._buildAvatar() (lines 1064-1077) always renders icons instead of using this URL. If this is preparation for future profile pictures, consider adding a TODO comment.

@SharkyBytes
Copy link
Copy Markdown
Collaborator

Do address the changes if anything relevant comes up and ping me on discord channel with the PR after this

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the Flutter chat feature so ChatScreen owns chat state/logic, while HomeScreen focuses on navigation and (optionally) forwarding an initial chat message via arguments.

Changes:

  • Removed chat controllers/message state from HomeScreen and rebuilt tabs with IndexedStack.
  • Added “initial message” queuing/flush logic and improved speech-to-text dialog handling in ChatScreen.
  • Restructured ChatScreen UI into header / message list / input bar with typing indicator and item cards.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
lib/screens/home/home_screen.dart Removes chat state from Home and forwards initial chat args into ChatScreen.
lib/screens/chat/chat_screen.dart Centralizes chat state and adds queuing for initial/queued messages plus speech/UI refactors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/screens/chat/chat_screen.dart (1)

1431-1449: ⚠️ Potential issue | 🟡 Minor

avatarUrl field is declared but never used.

The avatarUrl field is added to ChatMessage at line 1438 but is not utilized anywhere in the codebase. The _buildAvatar() method (lines 1282–1304) does not receive the ChatMessage instance and instead renders avatars with hardcoded default icons. Either remove this unused field or add a TODO comment if it's intended for future use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/chat/chat_screen.dart` around lines 1431 - 1449, The
ChatMessage.avatarUrl field is declared but unused; either remove it or wire it
into avatar rendering: update the avatar rendering flow by changing _buildAvatar
to accept a ChatMessage (or avatarUrl string) and use ChatMessage.avatarUrl to
render a NetworkImage (with placeholder/fallback to the existing default icon)
for user/assistant avatars, or remove the avatarUrl property from the
ChatMessage class entirely if it's not needed; update any constructors/usages of
ChatMessage accordingly (refer to ChatMessage, avatarUrl, and _buildAvatar).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/screens/chat/chat_screen.dart`:
- Around line 369-385: Duplicate UUID regex and team-member-resolution code
exists in _createTask, _createTicket, _queryTasks, _queryTickets, and
_modifyItem; extract into a private helper and shared regex: add a static final
RegExp named _uuidRegex and implement a private method
_resolveTeamMemberId(String? assignedTo) that returns null or the resolved
member id by (1) returning assignedTo if _uuidRegex matches, (2) trying exact
full_name match on _teamMembers, (3) then partial contains match, and (4) then
first-name-only match, and update each of the callers (_createTask,
_createTicket, _queryTasks, _queryTickets, _modifyItem) to use
_resolveTeamMemberId(assignedTo) instead of duplicating the logic.

In `@lib/screens/home/home_screen.dart`:
- Around line 61-78: IndexedStack eagerly builds all entries (including
ChatScreen), causing unnecessary startup cost; change HomeScreen to lazily
construct ChatScreen instead: stop instantiating ChatScreen in the screens list
and instead render it conditionally (e.g. keep a bool _chatInitialized; in the
tab-change handler setState(() => _chatInitialized = true) when _selectedIndex
becomes the chat tab index; in the build replace the ChatScreen entry with a
placeholder (SizedBox) unless _chatInitialized is true, and when true create
ChatScreen(key: const PageStorageKey('chat_screen'), arguments: _chatArgs));
keep using _selectedIndex to drive UI so state is preserved after the first
construction.
- Around line 44-48: The code currently sets _chatArgs only when _selectedIndex
== _chatTabIndex, so incoming initialMessage is dropped if the user is on
another tab; change the logic in HomeScreen (where _selectedIndex,
_chatTabIndex, _chatArgs are used) to always capture and store the
initialMessage when it's a non-empty String (keep the trim() and isNotEmpty
check) regardless of the current selected tab, so that the message is available
later when navigating to the chat tab.

---

Outside diff comments:
In `@lib/screens/chat/chat_screen.dart`:
- Around line 1431-1449: The ChatMessage.avatarUrl field is declared but unused;
either remove it or wire it into avatar rendering: update the avatar rendering
flow by changing _buildAvatar to accept a ChatMessage (or avatarUrl string) and
use ChatMessage.avatarUrl to render a NetworkImage (with placeholder/fallback to
the existing default icon) for user/assistant avatars, or remove the avatarUrl
property from the ChatMessage class entirely if it's not needed; update any
constructors/usages of ChatMessage accordingly (refer to ChatMessage, avatarUrl,
and _buildAvatar).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8df331d-1962-4968-97a2-46329b0b555a

📥 Commits

Reviewing files that changed from the base of the PR and between 617f004 and 899cb06.

📒 Files selected for processing (2)
  • lib/screens/chat/chat_screen.dart
  • lib/screens/home/home_screen.dart

@g-k-s-03 g-k-s-03 force-pushed the fix/chat-state-ownership branch from 899cb06 to 7ef247c Compare March 22, 2026 14:20
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/screens/home/home_screen.dart (1)

57-77: ⚠️ Potential issue | 🟠 Major

IndexedStack still initializes chat while the tab is hidden.

ChatScreen is always inserted into the IndexedStack, and Flutter only paints the selected IndexedStack child rather than deferring the others; initState() still runs when a child state is inserted into the tree. With lib/screens/chat/chat_screen.dart, Lines 40-76 starting the waveform animation, speech setup, and service initialization in initState(), and Lines 167-176 flushing _pendingMessage into _sendMessage(), a hidden chat tab can start work and even dispatch the initial message before the user ever opens Chat. Lazy-create ChatScreen the first time the chat tab is selected, then keep it mounted after that. (api.flutter.dev)

💡 One way to defer chat initialization
 class _HomeScreenState extends State<HomeScreen> {
   static const int _chatTabIndex = 3;
   static const int _tabCount = 5;
 
   int _selectedIndex = 0;
+  bool _chatInitialized = false;
   Map<String, dynamic>? _chatArgs;
 
   `@override`
   void initState() {
     super.initState();
@@
       if (initialMessage is String && initialMessage.trim().isNotEmpty) {
         _chatArgs = {'initial_message': initialMessage.trim()};
       }
     }
+
+    _chatInitialized = _selectedIndex == _chatTabIndex;
   }
 
   void _onTap(int index) {
-    setState(() => _selectedIndex = index);
+    setState(() {
+      _selectedIndex = index;
+      if (index == _chatTabIndex) {
+        _chatInitialized = true;
+      }
+    });
   }
@@
-      ChatScreen(
-        key: const PageStorageKey('chat_screen'),
-        arguments: _chatArgs,
-      ),
+      _chatInitialized
+          ? ChatScreen(
+              key: const PageStorageKey('chat_screen'),
+              arguments: _chatArgs,
+            )
+          : const SizedBox.shrink(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/home/home_screen.dart` around lines 57 - 77, IndexedStack
currently always constructs ChatScreen (key: PageStorageKey('chat_screen')) so
its initState runs even when hidden; change the build to lazily create
ChatScreen only when the chat tab is selected and then keep it mounted
thereafter: add a field like Widget? _chatScreen and a bool/_chatInitialized
flag, on tab change when _selectedIndex equals the chat tab index instantiate
_chatScreen = ChatScreen(key: const PageStorageKey('chat_screen'), arguments:
_chatArgs) (if null), and in the children list replace the eager ChatScreen
entry with _chatScreen ?? const SizedBox.shrink() (or a lightweight placeholder)
so ChatScreen is only constructed on first selection but remains in the
IndexedStack after creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lib/screens/home/home_screen.dart`:
- Around line 57-77: IndexedStack currently always constructs ChatScreen (key:
PageStorageKey('chat_screen')) so its initState runs even when hidden; change
the build to lazily create ChatScreen only when the chat tab is selected and
then keep it mounted thereafter: add a field like Widget? _chatScreen and a
bool/_chatInitialized flag, on tab change when _selectedIndex equals the chat
tab index instantiate _chatScreen = ChatScreen(key: const
PageStorageKey('chat_screen'), arguments: _chatArgs) (if null), and in the
children list replace the eager ChatScreen entry with _chatScreen ?? const
SizedBox.shrink() (or a lightweight placeholder) so ChatScreen is only
constructed on first selection but remains in the IndexedStack after creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 696db335-2d96-465a-93ce-a6271f126e89

📥 Commits

Reviewing files that changed from the base of the PR and between 899cb06 and 7ef247c.

⛔ Files ignored due to path filters (6)
  • linux/flutter/generated_plugin_registrant.cc is excluded by !**/linux/**
  • linux/flutter/generated_plugins.cmake is excluded by !**/linux/**
  • macos/Flutter/GeneratedPluginRegistrant.swift is excluded by !**/macos/**
  • pubspec.lock is excluded by !**/*.lock
  • windows/flutter/generated_plugin_registrant.cc is excluded by !**/windows/**
  • windows/flutter/generated_plugins.cmake is excluded by !**/windows/**
📒 Files selected for processing (2)
  • lib/screens/home/home_screen.dart
  • pubspec.yaml

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
lib/screens/chat/chat_screen.dart (1)

369-381: ⚠️ Potential issue | 🟠 Major

Use one assignee resolver and fail closed on lookup misses.

create_task / create_ticket only accept UUIDs or exact full-name matches, so partial names can silently create unassigned items. query_tasks / query_tickets are broader, but an unresolved assigned_to_team_member falls through to the general branch and can return unrelated records instead of no matches. These flows should share one resolver, and the query methods should short-circuit when a requested assignee cannot be resolved.

Also applies to: 404-417, 463-500, 515-552

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/chat/chat_screen.dart` around lines 369 - 381, Consolidate the
assignee-resolution logic into a single helper (e.g., a private method like
_resolveAssigneeToUuid) that takes the raw assignedTo string, checks the UUID
regex, and otherwise looks up _teamMembers for an exact full_name match; return
null on any miss. Replace the duplicated inline resolution blocks (seen around
the assignedToUserId logic and the other occurrences) to call that helper from
create_task/create_ticket/query_tasks/query_tickets and ensure create_* methods
only accept the returned UUID or treat as unassigned, while query_* methods must
short-circuit and return no results when the helper returns null (i.e., fail
closed instead of falling through to the general branch). Ensure the helper
references _teamMembers and returns the matching member['id'] or null to enforce
consistent behavior across the listed ranges.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/screens/chat/chat_screen.dart`:
- Around line 49-52: The waveform AnimationController (_waveformController),
currently started with ..repeat() in initState(), must not run for the screen's
whole lifetime; modify initialization to create the controller in initState
without calling repeat(), then start _waveformController.repeat() when the
listening dialog opens (wherever you show the dialog / in the method that
triggers showListeningDialog) and call _waveformController.stop() and
_waveformController.reset() when the dialog closes (and ensure dispose() still
calls _waveformController.dispose()); update any dialog open/close handlers to
control the controller so the ticker only runs while the dialog is visible.
- Around line 40-43: The current single-slot buffer _pendingMessage and the
_sendMessage() logic allow submits before _servicesReady or while _isProcessing
to be lost or overwrite earlier inputs; replace _pendingMessage with a FIFO
queue (e.g., List<String> _pendingMessages) and update _sendMessage(), any
initial-submit handling around _initialMessageConsumed, and the
generateChatResponse() entry points to enqueue the text when !_servicesReady or
_isProcessing, then process the queue in order once services are ready and when
processing completes (drain one at a time or loop while !_isProcessing &&
_pendingMessages.isNotEmpty), ensuring _initialMessageConsumed is set
appropriately and no submit ever calls generateChatResponse() directly while
services are uninitialized.

---

Duplicate comments:
In `@lib/screens/chat/chat_screen.dart`:
- Around line 369-381: Consolidate the assignee-resolution logic into a single
helper (e.g., a private method like _resolveAssigneeToUuid) that takes the raw
assignedTo string, checks the UUID regex, and otherwise looks up _teamMembers
for an exact full_name match; return null on any miss. Replace the duplicated
inline resolution blocks (seen around the assignedToUserId logic and the other
occurrences) to call that helper from
create_task/create_ticket/query_tasks/query_tickets and ensure create_* methods
only accept the returned UUID or treat as unassigned, while query_* methods must
short-circuit and return no results when the helper returns null (i.e., fail
closed instead of falling through to the general branch). Ensure the helper
references _teamMembers and returns the matching member['id'] or null to enforce
consistent behavior across the listed ranges.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 839331db-c726-4c93-bd4b-97a6bfa9ac42

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef247c and 2461560.

📒 Files selected for processing (1)
  • lib/screens/chat/chat_screen.dart

@g-k-s-03 g-k-s-03 force-pushed the fix/chat-state-ownership branch from 2461560 to c3800ef Compare March 22, 2026 14:53
@g-k-s-03
Copy link
Copy Markdown
Contributor Author

@SharkyBytes, @jddeep, can you merge this pr this pr is approved by Code Rabbit, as @SharkyBytes asked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Chat feature state is managed outside ChatScreen, causing architectural inconsistency

3 participants