From ef77858d82f6ae6fc397d56546105b014eab2aea Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 22 Mar 2026 07:46:08 +0100
Subject: [PATCH] fix: keyboard dismiss on tap outside, line breaks, session switch

---
 TODO.md                      |   37 +++++++++++++++++++++++++++++++++++++
 lib/widgets/input_bar.dart   |    3 +--
 lib/screens/chat_screen.dart |    9 +++++++--
 3 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..b2fc759
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,37 @@
+# PAILot Flutter - TODO
+
+## Pending Features
+
+### File Transfer (send/receive arbitrary files)
+- File picker in app (PDFs, Word docs, attachments, etc.)
+- New `file` message type in WebSocket protocol
+- Gateway handler to save received files and route to session
+- Session can send files back via `pailot_send_file`
+- Display file attachments in chat bubbles (icon + filename + tap to open)
+
+### Voice+Image Combined Message
+- When voice caption is recorded with images, hold images on server until voice transcript arrives
+- Deliver transcript + images together as one message to Claude
+- Ensures voice prefix sets reply channel correctly
+
+### Push Notifications (iOS APNs)
+- Notify when messages arrive while app is backgrounded/closed
+- Requires Apple Developer Portal APNs key setup
+- Server-side message queue for offline delivery
+
+### App Name Renaming (Runner → PAILot)
+- Rename Xcode target from Runner to PAILot (like Glidr did)
+- Update scheme names, bundle paths
+
+## Known Issues
+
+### Audio
+- Background audio may not survive full app termination (only screen lock)
+- Audio session category may conflict with phone calls
+
+### UI
+- Launch image still uses default Flutter placeholder
+- No app splash screen with PAILot branding
+
+### Navigation
+- vi keys (0, G, dd) are sent as literal text paste — works for Claude Code but may not for other terminals
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index f1d4436..b773415 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -364,9 +364,10 @@
   }
 
   Future<void> _switchSession(String sessionId) async {
-    // Stop any playing audio when switching sessions
+    // Stop any playing audio and dismiss keyboard when switching sessions
     await AudioService.stopPlayback();
     setState(() => _playingMessageId = null);
+    if (mounted) FocusScope.of(context).unfocus();
 
     ref.read(activeSessionIdProvider.notifier).state = sessionId;
     await ref.read(messagesProvider.notifier).switchSession(sessionId);
@@ -391,6 +392,7 @@
 
     ref.read(messagesProvider.notifier).addMessage(message);
     _textController.clear();
+    FocusScope.of(context).unfocus(); // dismiss keyboard
 
     // Send as plain text (not command) — gateway handles plain messages
     _ws?.send({
@@ -822,7 +824,9 @@
     final unreadCounts = ref.watch(unreadCountsProvider);
     final inputMode = ref.watch(inputModeProvider);
 
-    return Scaffold(
+    return GestureDetector(
+      onTap: () => FocusScope.of(context).unfocus(),
+      child: Scaffold(
       key: _scaffoldKey,
       appBar: AppBar(
         leading: IconButton(
@@ -927,6 +931,7 @@
           ),
         ],
       ),
+    ),
     );
   }
 }
diff --git a/lib/widgets/input_bar.dart b/lib/widgets/input_bar.dart
index 278534e..5ba3fa2 100644
--- a/lib/widgets/input_bar.dart
+++ b/lib/widgets/input_bar.dart
@@ -111,8 +111,7 @@
                   const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
               isDense: true,
             ),
-            textInputAction: TextInputAction.send,
-            onSubmitted: (_) => onSendText(),
+            textInputAction: TextInputAction.newline,
             maxLines: 4,
             minLines: 1,
           ),

--
Gitblit v1.3.1