diff --git a/.github/workflows/build-client.yml b/.github/workflows/build-client.yml new file mode 100644 index 0000000..81a8f40 --- /dev/null +++ b/.github/workflows/build-client.yml @@ -0,0 +1,33 @@ +name: Build ChatBox Client + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + workflow_dispatch: # Cho phép bấm chạy thủ công trên GitHub + +jobs: + build: + runs-on: windows-latest # Ứng dụng WPF bắt buộc phải build trên môi trường Windows + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET 10 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' # Cài đặt .NET 10 + + - name: Restore dependencies + run: dotnet restore ChatBox.Client/ChatBox.Client.csproj + + - name: Build and Publish Client + run: dotnet publish ChatBox.Client/ChatBox.Client.csproj -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false -o ./publish_exe + + - name: Upload Artifact (ChatBox.Client.exe) + uses: actions/upload-artifact@v4 + with: + name: ChatBox-Client-Windows + path: ./publish_exe/ diff --git a/.gitignore b/.gitignore index 0808c4a..0ec66df 100644 --- a/.gitignore +++ b/.gitignore @@ -480,3 +480,4 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp +.worktrees/ diff --git a/ChatBox.Client/Behaviors/SmoothScrollBehavior.cs b/ChatBox.Client/Behaviors/SmoothScrollBehavior.cs new file mode 100644 index 0000000..376849c --- /dev/null +++ b/ChatBox.Client/Behaviors/SmoothScrollBehavior.cs @@ -0,0 +1,75 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace ChatBox.Client.Behaviors +{ + public static class SmoothScrollBehavior + { + public static readonly DependencyProperty EnableSmoothScrollProperty = + DependencyProperty.RegisterAttached( + "EnableSmoothScroll", + typeof(bool), + typeof(SmoothScrollBehavior), + new PropertyMetadata(false, OnEnableSmoothScrollChanged)); + + public static bool GetEnableSmoothScroll(DependencyObject obj) => + (bool)obj.GetValue(EnableSmoothScrollProperty); + + public static void SetEnableSmoothScroll(DependencyObject obj, bool value) => + obj.SetValue(EnableSmoothScrollProperty, value); + + private static void OnEnableSmoothScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ScrollViewer scrollViewer) + { + if ((bool)e.NewValue) + { + scrollViewer.PreviewMouseWheel += OnPreviewMouseWheel; + } + else + { + scrollViewer.PreviewMouseWheel -= OnPreviewMouseWheel; + } + } + } + + public static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (sender is DependencyObject dobj) + { + var scrollViewer = dobj as ScrollViewer; + if (scrollViewer == null) + scrollViewer = FindVisualChild(dobj); + + if (scrollViewer != null) + { + double step = 38.0; + double targetOffset = scrollViewer.VerticalOffset - (System.Math.Sign(e.Delta) * step); + + if (targetOffset < 0) targetOffset = 0; + if (targetOffset > scrollViewer.ScrollableHeight) targetOffset = scrollViewer.ScrollableHeight; + + scrollViewer.ScrollToVerticalOffset(targetOffset); + e.Handled = true; + } + } + } + + private static T? FindVisualChild(DependencyObject obj) where T : DependencyObject + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) + { + DependencyObject child = VisualTreeHelper.GetChild(obj, i); + if (child != null && child is T t) + return t; + + T? childOfChild = FindVisualChild(child); + if (childOfChild != null) + return childOfChild; + } + return null; + } + } +} \ No newline at end of file diff --git a/ChatBox.Client/Converters/AvatarInitialsVisibilityConverter.cs b/ChatBox.Client/Converters/AvatarInitialsVisibilityConverter.cs new file mode 100644 index 0000000..ae09461 --- /dev/null +++ b/ChatBox.Client/Converters/AvatarInitialsVisibilityConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace ChatBox.Client.Converters +{ + public class AvatarInitialsVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string base64 && !string.IsNullOrEmpty(base64)) + return Visibility.Collapsed; + return Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ChatBox.Client/Converters/FirstLetterConverter.cs b/ChatBox.Client/Converters/FirstLetterConverter.cs new file mode 100644 index 0000000..68e0ce9 --- /dev/null +++ b/ChatBox.Client/Converters/FirstLetterConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace ChatBox.Client.Converters +{ + public class FirstLetterConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string name && !string.IsNullOrEmpty(name)) + { + char initial = char.ToUpper(name[0]); + return initial.ToString(); + } + return "?"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ChatBox.Client/MainWindow.xaml b/ChatBox.Client/MainWindow.xaml index a6e6b63..45ae207 100644 --- a/ChatBox.Client/MainWindow.xaml +++ b/ChatBox.Client/MainWindow.xaml @@ -9,6 +9,8 @@ + + + + + + + + + + +``` + +- [ ] **Step 2: Commit** + +--- + +## Task 5: Implement Image Staging Logic in MainWindow.xaml.cs + +**Files:** +- Modify: `ChatBox.Client/MainWindow.xaml.cs` + +- [ ] **Step 1: Add fields for pending images and display name** + +After line 128 (`private string _avatarBase64 = "";`), add: + +```csharp +private List _pendingImages = new(); +private string _displayName = ""; +``` + +- [ ] **Step 2: Update LoadOrGenerateConfig to set _displayName** + +In `LoadOrGenerateConfig()`, after the initials are set (lines 158-162), add: + +```csharp +_displayName = string.IsNullOrWhiteSpace(txtUsername.Text) ? "User" : txtUsername.Text.Trim(); +``` + +- [ ] **Step 3: Update TxtUsername_TextChanged to also set _displayName** + +At the start of `TxtUsername_TextChanged()`, before setting `lblFooterUsername`: + +```csharp +_displayName = string.IsNullOrWhiteSpace(txtUsername.Text) ? "User" : txtUsername.Text.Trim(); +``` + +- [ ] **Step 4: Add pending count update helper method** + +Add near the bottom of the class (before `FormatTimestamp`): + +```csharp +private void UpdatePendingImagesPanel() +{ + int count = _pendingImages.Count; + lblPendingCount.Text = $"{count} image{(count != 1 ? "s" : "")} pending"; + PendingImagesPanel.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed; + itemsPendingImages.ItemsSource = null; + itemsPendingImages.ItemsSource = _pendingImages; +} +``` + +- [ ] **Step 5: Add RemovePending handler** + +Add near other button handlers: + +```csharp +private void BtnRemovePending_Click(object sender, RoutedEventArgs e) +{ + if (sender is Button btn && btn.Tag is ChatMessage msg) + { + _pendingImages.Remove(msg); + UpdatePendingImagesPanel(); + } +} +``` + +- [ ] **Step 6: Update HandleImagePaste to stage instead of send** + +Replace `HandleImagePaste()` body with staging-only logic (no upload, no send): + +```csharp +private void HandleImagePaste() +{ + try + { + if (_pendingImages.Count >= 10) + { + MessageBox.Show("Maximum 10 images pending. Send or remove some first.", "Limit Reached", MessageBoxButton.OK, MessageBoxImage.Information); + return; + } + + var image = Clipboard.GetImage(); + if (image == null) return; + + string tempDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TempPaste"); + if (!Directory.Exists(tempDir)) + Directory.CreateDirectory(tempDir); + + string fileName = $"ClipboardImage_{DateTime.Now:yyyyMMdd_HHmmss}.png"; + string filePath = Path.Combine(tempDir, fileName); + + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder(); + encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(image)); + encoder.Save(fileStream); + } + + Guid fileId = Guid.NewGuid(); + var fileInfo = new FileInfo(filePath); + + var msg = new ChatMessage + { + Sender = _displayName, + Content = fileInfo.Name, + IsFile = true, + FileId = fileId.ToString(), + FileSize = fileInfo.Length, + AvatarBase64 = _avatarBase64, + IsTransferring = false, + IsMe = true, + Timestamp = FormatTimestamp(DateTime.UtcNow.ToString("O")), + LocalFilePath = filePath, + IsInImageChannel = true, + IsDraft = true + }; + + _pendingImages.Add(msg); + UpdatePendingImagesPanel(); + } + catch (Exception ex) + { + MessageBox.Show("Failed to paste image: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); + } +} +``` + +- [ ] **Step 7: Add SendPendingImages method** + +```csharp +private async void SendPendingImages() +{ + if (_pendingImages.Count == 0) return; + + var toSend = _pendingImages.ToList(); + _pendingImages.Clear(); + UpdatePendingImagesPanel(); + + foreach (var msg in toSend) + { + msg.IsDraft = false; + msg.IsTransferring = true; + _channelManager.AllMessages.Add(msg); + _channelManager.RefreshMessageList(); + + try + { + await _fileClient.UploadFileAsync(_connectionManager.ServerIp, msg.LocalFilePath, Guid.Parse(msg.FileId)); + msg.IsTransferring = false; + await _connectionManager.SendMessageAsync($"FILE_READY|{_userId}|{msg.FileId}|{msg.Content}|{msg.FileSize}"); + } + catch + { + msg.IsTransferring = false; + } + } + + _channelManager.RefreshMessageList(); +} +``` + +- [ ] **Step 8: Hook SendPendingImages to Enter key** + +Find the key-down handler for `txtChatInput` (or wherever Enter sends messages). Add `SendPendingImages()` call at the start of the Enter handling block, before sending text: + +```csharp +if (e.Key == Key.Enter && !isShift) +{ + e.Handled = true; + SendPendingImages(); // <-- ADD THIS + string text = txtChatInput.Text.Trim(); + if (!string.IsNullOrEmpty(text)) + { + await BtnSendChat_ClickInternal(text); + } + txtChatInput.Clear(); + return; +} +``` + +- [ ] **Step 9: Commit** + +--- + +## Task 6: Fix "Me" → Display Name in All Message Sending + +**Files:** +- Modify: `ChatBox.Client/MainWindow.xaml.cs` + +- [ ] **Step 1: Find all places where `Sender = "Me"` is set** + +Grep for `Sender = "Me"` — this appears in `HandleImagePaste()` which we already fixed, and may appear in other file-send methods. + +- [ ] **Step 2: Replace any remaining `Sender = "Me"` with `Sender = _displayName`** + +Also fix any other hardcoded "Me" in `HandleFileDragDrop()`, `HandleFileSelect()`, etc. + +- [ ] **Step 3: Commit** + +--- + +## Task 7: Full Integration and Test + +**Files:** +- All modified files + +- [ ] **Step 1: Build the solution** + +Run: `dotnet build ChatBox.sln` + +- [ ] **Step 2: Fix any compilation errors** + +- [ ] **Step 3: Test avatar fallback** + +1. Launch app without avatar set +2. Verify initials show in avatar circles (message avatars and footer) +3. Set an avatar and verify image shows instead + +- [ ] **Step 4: Test image staging** + +1. Ctrl+V an image — verify it appears in pending panel, NOT in chat +2. Add 2 more images — verify count shows "3 images pending" +3. Press Enter — verify all 3 images send together as real messages +4. Try adding 11th image — verify limit message appears + +- [ ] **Step 5: Test username display** + +1. Set a username like "Camellya" +2. Send a message — verify sender name shows "Camellya" not "Me" + +- [ ] **Step 6: Commit final** + +--- + +## Spec Coverage Checklist + +| Spec Section | Task(s) | Status | +|---|---|---| +| Avatar fallback (initials) | Task 2, Task 3 | | +| Image staging pending panel (max 10) | Task 4, Task 5 | | +| Username "Me" → display name | Task 5, Task 6 | | +| Pending panel removal (X button) | Task 5 (BtnRemovePending) | | +| Send on Enter | Task 5 (SendPendingImages) | | + +## Placeholder Scan + +- [x] No "TBD" or "TODO" in task steps +- [x] All code blocks show actual implementation +- [x] Exact file paths with line number hints +- [x] No "similar to X" references without repeating code \ No newline at end of file diff --git a/docs/superpowers/specs/2026-05-19-chatbox-ui-fixes-design.md b/docs/superpowers/specs/2026-05-19-chatbox-ui-fixes-design.md new file mode 100644 index 0000000..ce0f8de --- /dev/null +++ b/docs/superpowers/specs/2026-05-19-chatbox-ui-fixes-design.md @@ -0,0 +1,139 @@ +# ChatBox UI Fixes Design Spec +**Date:** 2026-05-19 +**Topic:** Avatar fallback, Image staging, Username display, Message alignment + +--- + +## 1. Issue Summary + +| # | Issue | Current | Desired | +|---|-------|---------|---------| +| 1 | Avatar fallback missing | Empty circle when no avatar uploaded | Show first letter of display name as initials | +| 2 | Image paste sends immediately | Ctrl+V uploads and sends right away | Stage up to 10 images inline as "drafts" until Enter is pressed | +| 3 | Sender name hardcoded as "Me" | Messages from self show "Me" | Show user's actual display name from profile | + +--- + +## 2. Avatar Fallback + +### Current State +- `AvatarBase64` is empty string when no avatar uploaded +- XAML binds `ImageBrush` to `AvatarBase64` → shows nothing when empty +- No fallback UI element for initials + +### Design +- Use `Ellipse` with solid color fill `#5865F2` as base layer +- Overlay `TextBlock` with first letter of display name (uppercase, white, bold) +- When `AvatarBase64` is non-empty → hide initials, show image instead +- Logic via `IValueConverter` or XAML triggers + +### Implementation +- Create `AvatarInitialsConverter : IValueConverter` that: + - Input: `AvatarBase64` string + - Output: `Visibility` → `Collapsed` if Base64 non-empty, `Visible` if empty +- Also need a converter for the image visibility (inverse) +- Initials text bound to sender name's first letter + +--- + +## 3. Image Staging (Pending Panel Approach) + +### Current State +- `HandleImagePaste()` in `MainWindow.xaml.cs` (line 651): + 1. Saves image to TempPaste folder + 2. Creates `ChatMessage` immediately + 3. Calls `_fileClient.UploadFileAsync()` immediately + 4. Calls `_connectionManager.SendMessageAsync("FILE_READY|...")` immediately + +### Design (Approach A - Pending Panel) +- **Pending panel**: A dedicated panel at the bottom of chat area, above the message input +- **Draft list**: `_pendingImages : List` (max 10) +- When Ctrl+V image detected: + 1. Save image to TempPaste (same as before) + 2. Create `ChatMessage` with `IsDraft = true` and `Sender = _displayName` + 3. Add to `_pendingImages` list and display in pending panel (with preview thumbnails) + 4. **DO NOT** upload or send yet +- When Enter pressed in message input: + 1. If `_pendingImages.Count > 0`, iterate and upload+send each + 2. Clear `_pendingImages` after sending +- Pending panel UI: + - Horizontal `WrapPanel` with small thumbnail previews (48x48 or 64x64) + - Small "X" button on each thumbnail to remove from queue + - Count indicator: "3 images pending" + - Panel background: subtle gray `#F2F3F5` + - Panel height: auto, max ~100px with scroll if needed + +### Max 10 Images +- Before adding new draft, check `_pendingImages.Count >= 10` +- If full, show toast: "Maximum 10 images pending. Send or remove some first." + +### Visual Difference: Draft vs Sent +| State | Border | Opacity | Progress | Send Indicator | +|-------|--------|---------|----------|---------------| +| Draft (thumbnail) | Solid purple | 1.0 | None | Small pending icon | +| Uploading | Solid | 1.0 | ProgressBar on thumbnail | Spinner overlay | +| Sent | Normal | 1.0 | Hidden | Normal message in chat | + +### Pending Panel Layout (XAML concept) +``` + + + + + +``` + +### Draft Thumbnail Item +- Small `Image` (64x64) with rounded corners +- Purple border when draft +- Red "X" button overlay top-right +- Progress ring when uploading +- Tooltip: filename + +--- + +## 4. Username Display + +### Current State +- `HandleImagePaste()` line 677: `Sender = "Me"` hardcoded +- `txtUsername` TextBox exists with default "User" +- No persistent user profile with display name + +### Design +- Store display name from `txtUsername.Text` when it changes +- Use stored display name for all new messages (`Sender = _displayName`) +- `HandleImagePaste()`: change `Sender = "Me"` → `Sender = _displayName` +- For incoming messages: use sender name from server packet + +### Implementation +- `_displayName : string` field in MainWindow +- `TxtUsername_TextChanged()`: update `_displayName` +- `HandleImagePaste()`: use `_displayName` instead of "Me" +- `BtnSendChat_Click()`: already uses bound data, just needs `_displayName` for avatar context + +--- + +## 5. File Changes + +### Files to Modify +| File | Changes | +|------|---------| +| `ChatBox.Client/MainWindow.xaml.cs` | Image staging logic, username field, HandleImagePaste update | +| `ChatBox.Client/MainWindow.xaml` | Alignment triggers, avatar fallback, draft visual style | +| `ChatBox.Client/ViewModels/ChatMessage.cs` | Add `IsDraft` property, maybe `PendingIndex` | +| `ChatBox.Client/Converters/Base64ImageConverter.cs` | May need to add/extend converters for avatar fallback | + +### New Files +| File | Purpose | +|------|---------| +| `ChatBox.Client/Converters/AvatarInitialsVisibilityConverter.cs` | Show initials when no avatar | +| `ChatBox.Client/Converters/FirstLetterConverter.cs` | Extract first letter from name for initials | + +--- + +## 6. Dependencies +- No database changes +- No network protocol changes (image staging is client-side only) +- Backward compatible with existing messages \ No newline at end of file diff --git a/docs/system-design.md b/docs/system-design.md new file mode 100644 index 0000000..ac19864 --- /dev/null +++ b/docs/system-design.md @@ -0,0 +1,349 @@ +# ChatBox System Design + +**Project:** LAN Chat Application (WPF/.NET 10) +**Date:** 2026-05-20 +**Status:** Analysis Complete + +--- + +## 1. Current Architecture + +### Solution Structure +``` +ChatBox.slnx (3 projects) +├── ChatBox.Client/ # WPF client (UI + socket client) +├── ChatBox.Server/ # WPF server (TCP listener + PostgreSQL) +└── LocalChat.Core/ # Shared library (models, services, contracts) +``` + +### Technology Stack +| Component | Technology | +|-----------|------------| +| Framework | .NET 10.0-windows, WPF | +| MVVM | CommunityToolkit.Mvvm | +| Database | PostgreSQL via EF Core + Npgsql | +| Networking | TCP Sockets (port 9999 chat, 10000 file transfer) | +| Emoji | Emoji.Wpf | +| Image Handling | Base64 encoding for avatars, chunked file transfer | + +--- + +## 2. Current Folder Structure + +### ChatBox.Client/ +``` +ChatBox.Client/ +├── App.xaml / App.xaml.cs +├── MainWindow.xaml / MainWindow.xaml.cs ⚠️ 1534 lines - TOO LARGE +├── Behaviors/ +│ └── SmoothScrollBehavior.cs +├── Converters/ +│ ├── AvatarInitialsVisibilityConverter.cs +│ ├── Base64ImageConverter.cs +│ └── FirstLetterConverter.cs +├── Managers/ +│ ├── ChannelManager.cs +│ ├── ClipboardPasteHandler.cs +│ ├── ConnectionManager.cs +│ ├── EmojiManager.cs +│ ├── FileTransferManager.cs +│ ├── GalleryManager.cs +│ ├── LightboxManager.cs +│ ├── MessageRouter.cs +│ └── WindowChromeManager.cs +└── ViewModels/ + └── ChatMessage.cs ⚠️ DUPLICATE - exists also in MainWindow.xaml.cs +``` + +### LocalChat.Core/ +``` +LocalChat.Core/ +├── Contracts/ +│ ├── IChatClient.cs +│ ├── IChatServer.cs +│ ├── IFileClient.cs +│ └── IFileServer.cs +├── Data/ +│ └── ChatDbContext.cs +├── Models/ +│ ├── ChatMessage.cs +│ ├── MessageReaction.cs +│ └── User.cs +└── Services/ + ├── ChatService.cs # Contains ChatServer + ChatClient classes + └── FileTransferService.cs # Contains FileServer + FileClient classes +``` + +--- + +## 3. Architecture Patterns Observed + +### Current Patterns +| Pattern | Implementation | +|---------|---------------| +| MVVM-light | ViewModels folder exists, but Client uses code-behind heavily | +| Manager Pattern | 9 managers in Client for UI concerns | +| Service Layer | Business logic in LocalChat.Core/Services | +| Contract/Interface | Interfaces in LocalChat.Core/Contracts | +| Converter Pattern | Value converters in Converters folder | +| Behavior Pattern | WPF attached behaviors in Behaviors folder | + +### Issues Identified +1. **Code-behind heavy** - MainWindow.xaml.cs (1534 lines) contains UI logic +2. **Duplicate classes** - ChatMessage exists in two locations +3. **No DI container** - Manual constructor injection only +4. **Large files** - Violates "keep files under 200 lines" rule + +--- + +## 4. Recommended Folder Structure + +### Clean Architecture Proposal + +``` +ChatBox/ +├── ChatBox.Client/ +│ ├── App.xaml / App.xaml.cs +│ ├── MainWindow.xaml / MainWindow.xaml.cs # Keep minimal +│ ├── Views/ +│ │ ├── Controls/ +│ │ │ ├── MessageBubble.xaml +│ │ │ ├── UserAvatar.xaml +│ │ │ ├── EmojiPicker.xaml +│ │ │ └── ReactionPopup.xaml +│ │ ├── Overlays/ +│ │ │ ├── LightboxOverlay.xaml +│ │ │ └── SettingsOverlay.xaml +│ │ └── Components/ +│ │ ├── ChatMessageList.xaml +│ │ ├── ImageGallery.xaml +│ │ ├── FileGallery.xaml +│ │ ├── OnlineUsersPanel.xaml +│ │ └── ChannelSidebar.xaml +│ ├── ViewModels/ +│ │ ├── MainViewModel.cs +│ │ ├── ChatViewModel.cs +│ │ ├── MessageViewModel.cs +│ │ ├── SettingsViewModel.cs +│ │ └── GalleryViewModel.cs +│ ├── Models/ # Client-side models +│ │ └── LocalChatMessage.cs +│ ├── Services/ # Client services (if needed) +│ ├── Behaviors/ +│ ├── Converters/ +│ └── Managers/ # UI-specific managers +│ ├── ConnectionManager.cs +│ ├── ImageGalleryManager.cs +│ └── LightboxManager.cs +│ +├── ChatBox.Server/ +│ ├── App.xaml / App.xaml.cs +│ ├── MainWindow.xaml / MainWindow.xaml.cs +│ ├── Views/ +│ │ └── ServerDashboard.xaml +│ └── ViewModels/ +│ └── ServerViewModel.cs +│ +└── LocalChat.Core/ + ├── Contracts/ + ├── Models/ + ├── Data/ + └── Services/ + ├── ChatServer.cs + ├── ChatClient.cs + ├── FileServer.cs + └── FileClient.cs +``` + +### Key Changes +1. **Views/Controls/** - Reusable XAML user controls (MessageBubble, UserAvatar, etc.) +2. **Views/Components/** - Composite components (MessageList, Gallery, etc.) +3. **ViewModels/** - Formal MVVM ViewModels with commands and properties +4. **Models/** - Client-side models separate from Core models + +--- + +## 5. UI/UX Design + +### Color Palette (Current Discord-Inspired) +| Element | Color | Hex | +|---------|-------|-----| +| Background Primary | Dark | `#060607` | +| Background Secondary | Dark Gray | `#1E1F22` | +| Background Tertiary | Medium Gray | `#2B2D31` | +| Text Primary | White | `#FFFFFF` | +| Text Secondary | Light Gray | `#B5BAC1` | +| Text Muted | Gray | `#6D6F78` | +| Online Status | Green | `#23A55A` | +| Offline Status | Gray | `#747F8D` | +| Error/Disconnect | Red | `#ED4245` | +| Accent Active | Light Gray | `#E3E5E8` | + +### Layout Structure + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Title Bar (Custom Chrome - Drag, Min/Max/Close) │ +├─────────────┬───────────────────────────────────────────────────┤ +│ │ Room Name Header [Info] [Emoji] │ +│ Channels ├───────────────────────────────────────────────────┤ +│ ───────── │ │ +│ # chat │ Message List (Virtualized) │ +│ # images │ ┌─────────────────────────────────────────────┐ │ +│ # files │ │ [Avatar] Username timestamp │ │ +│ │ │ Message content here... │ │ +│ Online │ │ [Reactions] [React] │ │ +│ ───────── │ └─────────────────────────────────────────────┘ │ +│ User1 │ │ +│ User2 (You)│ ┌─────────────────────────────────────────────┐ │ +│ │ │ My messages aligned right │ │ +│ │ └─────────────────────────────────────────────┘ │ +├─────────────┴───────────────────────────────────────────────────┤ +│ Input Area (TextBox + Upload + Send) │ +├─────────────────────────────────────────────────────────────────┤ +│ Footer: Avatar Initials | Username | Online Status │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### UI Components + +| Component | Description | +|-----------|-------------| +| MessageBubble | Rounded rect with avatar, sender name, timestamp, content, reactions | +| EmojiPicker | 6-category emoji grid popup (Smileys, Animals, Food, Activities, Travel, Objects) | +| ReactionPopup | Quick emoji reaction buttons on message hover | +| LightboxOverlay | Full-screen image viewer with download option | +| SettingsOverlay | Username/avatar/IP configuration panel | + +### Interaction Patterns + +| Action | Behavior | +|--------|----------| +| Send Message | Enter key or Send button | +| Paste Image | Ctrl+V with image in clipboard | +| Drag & Drop | Files to upload | +| Emoji Reaction | Click reaction button on message | +| View Image | Click thumbnail to open lightbox | +| Channel Switch | Click channel in sidebar | +| Edit Profile | Info button → Settings overlay | + +--- + +## 6. Data Flow + +### Message Flow +``` +[Client] [Server] [Database] + │ │ │ + │──── JOIN ─────────────────>│ │ + │ │──── Save User ──────────────>│ + │ │<─── Load History ────────────│ + │<─── History + Room ───────│ │ + │ │ │ + │──── MSG ─────────────────>│ │ + │ │──── Save Message ───────────>│ + │ │<─────────────────────────────│ + │<─── Broadcast ────────────│ │ + │ │ │ + │──── REACT ───────────────>│ │ + │ │──── Save/Remove Reaction ───>│ + │<─── REACTION_UPDATE ──────│ │ +``` + +### File Transfer Flow +``` +[Client] [Server] [Storage] + │ │ │ + │──── FILE_READY ───────>│ │ + │<─── FILE_READY ────────│ (broadcast to others) │ + │ │ │ + │==== Chunk 1 upload =====>│ │ + │==== Chunk 2 upload =====>│ │ + │==== Chunk N upload =====>│ │ + │ │==== Save to disk ─────────>│ +``` + +--- + +## 7. Network Protocol + +### Message Types +| Type | Format | Direction | +|------|--------|-----------| +| JOIN | `JOIN\|UserId\|Username\|AvatarBase64` | C→S | +| MSG | `MSG\|UserId\|Content` | C→S | +| FILE_READY | `FILE_READY\|UserId\|FileId\|FileName\|Size` | C→S | +| REACT | `REACT\|UserId\|MessageId\|Emoji` | C→S | +| UPDATE_PROFILE | `UPDATE_PROFILE\|UserId\|Username\|AvatarBase64` | C→S | +| ROOM_NAME | `ROOM_NAME\|RoomName` | S→C | +| GREETING | `GREETING\|Message` | S→C | +| ONLINE_USERS | `ONLINE_USERS\|User1,User2,...` | S→C | +| REACTION_UPDATE | `REACTION_UPDATE\|MessageId\|ReactionsJson` | S→C | + +### File Transfer Protocol +- **Port:** 10000 +- **Header:** 29 bytes (Action + FileId + Offset + Length) +- **Chunk Size:** 4MB +- **Parallel:** 4 concurrent chunks + +--- + +## 8. Database Schema + +### Entities +``` +User +├── Id (PK, string) +├── Username (string, max 100) +├── AvatarBase64 (string, nullable) +└── LastSeen (datetime) + +ChatMessage +├── Id (PK, string) +├── SenderId (FK → User) +├── Content (string) +├── Timestamp (datetime) +├── IsFile (bool) +├── FileId (string, nullable) +├── FileSize (long) +└── Reactions (collection) + +MessageReaction +├── Id (PK, string) +├── MessageId (FK → ChatMessage) +├── UserId (FK → User) +├── Emoji (string, max 50) +├── CreatedAt (datetime) +└── Unique index on (MessageId, UserId, Emoji) +``` + +--- + +## 9. Key Issues & Recommendations + +### Critical Issues +1. **MainWindow.xaml.cs too large** (1534 lines) → Split into ViewModels + Views +2. **Duplicate ChatMessage class** → Consolidate into single location +3. **No DI container** → Consider Microsoft.Extensions.DependencyInjection +4. **No proper MVVM** → Create ViewModels with RelayCommand from CommunityToolkit.Mvvm + +### Recommendations +1. Extract UI logic from MainWindow.xaml.cs into MainViewModel +2. Create reusable XAML user controls for MessageBubble, EmojiPicker, ReactionPopup +3. Implement ICommand pattern for all button clicks +4. Add VirtualizingStackPanel for message list performance +5. Consider adding unit tests + +--- + +## 10. Unresolved Questions + +1. Should client have local SQLite cache for offline message history? +2. Do you want to add typing indicators? +3. Should there be message search functionality? +4. Any planned features like DMs, channels, roles? +5. Should file transfer support pause/resume? + +--- + +*Document generated from codebase analysis. To implement recommended changes, see phase plan.* \ No newline at end of file diff --git a/plans/260519-1520-emoji-reactions-feature/plan.md b/plans/260519-1520-emoji-reactions-feature/plan.md new file mode 100644 index 0000000..71a60bd --- /dev/null +++ b/plans/260519-1520-emoji-reactions-feature/plan.md @@ -0,0 +1,124 @@ +# Emoji Reactions Feature - Implementation Plan + +## Context +- **Project:** ChatBox - Local LAN chat application with WPF (.NET), MVVM-light, PostgreSQL +- **Feature:** Add emoji reactions to messages (like Discord) +- **User Requirements:** + - Emoji picker popup on right-click/long-press + - Heart emoji with no color when not hovered, colored when hovered + - Show 5-6 emoji options + - Persistent storage in PostgreSQL + real-time broadcast to all users + - Show reactions on messages (who reacted) + +## Architecture Overview + +``` +Client (WPF) Server (WPF + Console) + | | + |--- TCP 9999 (Chat) ----------------> | + |<--- Broadcast/Messages -------------- | + | | + |--------- REACT||------> | + |<------- REACTION_UPDATE ------------ | + | | + PostgreSQL Database + (Stores Users, ChatMessages, Reactions) +``` + +## Message Protocol Extensions + +### New Messages +``` +REACT|MessageId|Emoji +REACTION_UPDATE|MessageId|ReactionsJson +``` + +### ReactionsJson Format +```json +[{"emoji":"👍","users":["user1","user2"]},{"emoji":"❤️","users":["user3"]}] +``` + +## Database Schema + +### New Table: MessageReaction +```sql +CREATE TABLE message_reactions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + message_id UUID NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + emoji VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(message_id, user_id, emoji) +); + +CREATE INDEX idx_message_reactions_message_id ON message_reactions(message_id); +``` + +## Tasks + +### Task 1: Database Schema Update +- **Files:** `LocalChat.Core\Data\ChatDbContext.cs` +- Add `MessageReaction` entity +- Add `Reactions` navigation property to `ChatMessage` +- Add unique constraint (message_id, user_id, emoji) +- Run migration or ensure table creation + +### Task 2: Message Model Update +- **Files:** `LocalChat.Core\Models\ChatMessage.cs` +- Add `Reactions` property (list of MessageReaction) +- Add `ReactionSummary` computed property for UI display + +### Task 3: Server - Reaction Handling +- **Files:** `LocalChat.Core\Services\ChatService.cs` +- Add `ProcessReactionAsync` method +- Add `BroadcastReactionUpdate` method +- Parse `REACT|MessageId|Emoji` message +- Store reaction in database +- Broadcast to all clients + +### Task 4: Client - Reaction Popup UI +- **Files:** `ChatBox.Client\MainWindow.xaml` +- Create `ReactionPopup` custom control or Popup +- 5-6 emoji options: 👍 👎 😂 ❤️ 😮 😢 +- Heart emoji with hover effect (no color → colored) +- Position popup near the message + +### Task 5: Client - Right-Click Handler +- **Files:** `ChatBox.Client\MainWindow.xaml.cs` +- Add `Message_MouseRightButtonDown` handler +- Show emoji popup at cursor position +- Handle emoji selection +- Send `REACT` message to server + +### Task 6: Client - Display Reactions on Messages +- **Files:** `ChatBox.Client\MainWindow.xaml` +- Add reactions display below message content +- Show emoji + count + user names tooltip +- Update UI when `REACTION_UPDATE` received +- Refresh message list + +### Task 7: Client - Send Reaction +- **Files:** `ChatBox.Client\MainWindow.xaml.cs` +- Parse and handle `REACTION_UPDATE` messages +- Update local ChatMessage with reactions +- Refresh message list + +## Dependencies +- Task 1 → Task 3 (server needs database) +- Task 1 → Task 2 (model needs entity) +- Task 2, 3 → Task 7 (client needs server) +- Task 5, 7 → Task 6 (popup and receiving need display) + +## Status +- [ ] Task 1: Database Schema Update +- [ ] Task 2: Message Model Update +- [ ] Task 3: Server - Reaction Handling +- [ ] Task 4: Client - Reaction Popup UI +- [ ] Task 5: Client - Right-Click Handler +- [ ] Task 6: Client - Display Reactions on Messages +- [ ] Task 7: Client - Send Reaction + +## Next Steps +1. Create worktree for feature branch +2. Execute tasks sequentially using subagent-driven development +3. Each task: implement → spec review → code quality review → commit \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-01-split-mainwindow.md b/plans/260520-1520-system-design-refactoring/phase-01-split-mainwindow.md new file mode 100644 index 0000000..a368888 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-01-split-mainwindow.md @@ -0,0 +1,122 @@ +--- +title: "Phase 1 - Split MainWindow" +description: "Split MainWindow.xaml.cs (1534 lines) into MainViewModel and supporting classes" +status: pending +priority: P1 +effort: 4h +branch: feature/emoji-reactions +tags: [refactoring, mvvm, mainwindow] +created: 2026-05-20 +--- + +# Phase 1: Split MainWindow into ViewModels + +Extract code-behind logic from MainWindow.xaml.cs into MainViewModel and supporting classes. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) + +## Overview + +MainWindow.xaml.cs (1534 lines) contains UI logic, socket handling, clipboard, emoji, gallery, file transfer, and connection management all in one file. This violates the 200-line rule and makes the code hard to maintain. + +## Key Insights + +- MainWindow.xaml.cs handles: socket messages, UI state, clipboard paste, emoji selection, lightbox, file transfer, gallery management +- Code uses _prefix for private fields and direct UI element access (this.messageList) +- Current architecture uses "Manager" classes for some concerns but still keeps too much logic in code-behind +- Need to extract into: MainViewModel, MessageListViewModel, InputAreaViewModel, StatusBarViewModel + +## Requirements + +### Functional +- Extract message handling logic to MessageListViewModel +- Extract input area logic (text, clipboard, send) to InputAreaViewModel +- Extract connection status to StatusBarViewModel +- Keep MainWindow.xaml.cs minimal (~200 lines) + +### Non-Functional +- No runtime behavior changes +- All existing keyboard shortcuts and interactions preserved +- Maintain current color scheme and layout + +## Architecture + +``` +MainWindow.xaml.cs (reduced) +├── MainViewModel +│ ├── MessageListViewModel +│ │ ├── Messages: ObservableCollection +│ │ ├── SelectedMessage: MessageViewModel +│ │ └── AddMessage(), RemoveMessage() +│ ├── InputAreaViewModel +│ │ ├── InputText: string +│ │ ├── SendCommand: ICommand +│ │ └── PasteImageCommand: ICommand +│ └── StatusBarViewModel +│ ├── Username: string +│ ├── AvatarInitials: string +│ └── IsOnline: bool +``` + +## Related Code Files + +### Files to Modify +- `ChatBox.Client/MainWindow.xaml.cs` +- `ChatBox.Client/MainWindow.xaml` + +### Files to Create +- `ChatBox.Client/ViewModels/MainViewModel.cs` +- `ChatBox.Client/ViewModels/MessageListViewModel.cs` +- `ChatBox.Client/ViewModels/InputAreaViewModel.cs` +- `ChatBox.Client/ViewModels/StatusBarViewModel.cs` + +## Implementation Steps + +1. **Read MainWindow.xaml.cs** to understand current structure and identify extraction points +2. **Create folder structure** - `ChatBox.Client/ViewModels/` +3. **Create MainViewModel.cs** - Extract MainWindow field declarations and initialization +4. **Create MessageListViewModel.cs** - Extract message list management (AddMessage, RemoveMessage, messages collection) +5. **Create InputAreaViewModel.cs** - Extract input text, send command, paste handling +6. **Create StatusBarViewModel.cs** - Extract username, avatar, online status properties +7. **Wire up MainWindow.xaml** - DataContext = MainViewModel, update bindings +8. **Update MainWindow.xaml.cs** - Remove extracted logic, keep window management only +9. **Compile and verify** - Ensure no breaking changes + +## Todo List + +- [ ] Read MainWindow.xaml.cs to identify extraction points +- [ ] Create ViewModels folder +- [ ] Create MainViewModel.cs +- [ ] Create MessageListViewModel.cs +- [ ] Create InputAreaViewModel.cs +- [ ] Create StatusBarViewModel.cs +- [ ] Wire up MainWindow.xaml DataContext +- [ ] Reduce MainWindow.xaml.cs to ~200 lines +- [ ] Compile and verify existing functionality + +## Success Criteria + +- MainWindow.xaml.cs under 200 lines +- All extracted ViewModels compile without errors +- MainWindow DataContext bound to MainViewModel +- All existing functionality preserved (send message, receive message, clipboard paste) + +## Risk Assessment + +- **Risk**: Breaking existing message flow during extraction +- **Mitigation**: Extract one concern at a time, compile after each, test manually +- **Risk**: Large number of UI element references in code-behind +- **Mitigation**: Use x:Name bindings to ViewModel properties, remove direct element access + +## Security Considerations + +- No new network access or data handling in this phase +- Preserve existing input validation + +## Next Steps + +- [Phase 2](./phase-02-cleanup-duplicates.md) - Remove duplicate ChatMessage classes +- [Phase 3](./phase-03-mvvm-refactor.md) - Create proper ViewModels with RelayCommand \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-02-cleanup-duplicates.md b/plans/260520-1520-system-design-refactoring/phase-02-cleanup-duplicates.md new file mode 100644 index 0000000..9aa6fb3 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-02-cleanup-duplicates.md @@ -0,0 +1,118 @@ +--- +title: "Phase 2 - Cleanup Duplicates" +description: "Remove duplicate ChatMessage classes, consolidate into LocalChat.Core/Models" +status: pending +priority: P1 +effort: 2h +branch: feature/emoji-reactions +tags: [refactoring, models, cleanup] +created: 2026-05-20 +--- + +# Phase 2: Cleanup Duplicate Classes + +Remove duplicate ChatMessage class definitions and consolidate into single location. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) +- [Phase 1](./phase-01-split-mainwindow.md) + +## Overview + +ChatMessage exists in two locations: +1. `LocalChat.Core/Models/ChatMessage.cs` (canonical, used by EF Core) +2. `ChatBox.Client/ViewModels/ChatMessage.cs` (duplicate) +3. Inline in MainWindow.xaml.cs (duplicate fields) + +This duplication causes maintenance issues and confusion about which class to use. + +## Key Insights + +- Core/Models/ChatMessage.cs is used by EF Core for database operations +- Client/ViewModels/ChatMessage.cs is a lighter-weight client-side model +- MainWindow.xaml.cs inline defines sender ID, content, timestamp fields +- Need to decide: use Core model client-side OR create shared client model + +## Requirements + +### Functional +- Remove duplicate ChatMessage from ViewModels folder +- Remove inline ChatMessage definition from MainWindow.xaml.cs +- Ensure all usages reference LocalChat.Core/Models/ChatMessage +- Add any client-specific properties as partial/extension + +### Non-Functional +- No runtime behavior changes +- All message serialization/deserialization preserved + +## Architecture + +``` +Before (Duplicate): +├── LocalChat.Core/Models/ChatMessage.cs ← Entity for EF Core +├── ChatBox.Client/ViewModels/ChatMessage.cs ← Duplicate +└── MainWindow.xaml.cs inline fields ← Duplicate + +After (Consolidated): +└── LocalChat.Core/Models/ChatMessage.cs ← Single source of truth +``` + +## Related Code Files + +### Files to Modify +- `ChatBox.Client/MainWindow.xaml.cs` - Remove inline Message class +- `ChatBox.Client/ViewModels/ChatMessage.cs` - Delete file + +### Files to Check for Usages +- `ChatBox.Client/MainWindow.xaml.cs` +- `ChatBox.Client/ViewModels/` (any files referencing ChatMessage) +- `ChatBox.Client/Managers/` (any managers using ChatMessage) + +## Implementation Steps + +1. **Search for all ChatMessage usages** in ChatBox.Client +2. **Read LocalChat.Core/Models/ChatMessage.cs** to understand structure +3. **Read ChatBox.Client/ViewModels/ChatMessage.cs** to see differences +4. **Check if client has extra properties** not in Core model +5. **If client has extra properties**, extend Core model or create LocalChatMessage client model +6. **Update all references** to use Core model +7. **Delete duplicate files** +8. **Compile and verify** + +## Todo List + +- [ ] Search for all ChatMessage usages in ChatBox.Client +- [ ] Read existing ChatMessage classes in both locations +- [ ] Identify any client-specific properties missing from Core model +- [ ] Extend Core model if needed (add client properties) +- [ ] Update all references in MainWindow.xaml.cs +- [ ] Update all references in ViewModels +- [ ] Update all references in Managers +- [ ] Delete ChatBox.Client/ViewModels/ChatMessage.cs +- [ ] Delete any inline Message class in MainWindow.xaml.cs +- [ ] Compile and verify + +## Success Criteria + +- Only one ChatMessage definition exists (in LocalChat.Core/Models/) +- No compile errors from reference changes +- All message handling uses consolidated model + +## Risk Assessment + +- **Risk**: Client needs properties not in Core model +- **Mitigation**: Add missing properties to Core model (they may be useful for server too) +- **Risk**: Breaking message serialization +- **Mitigation**: Verify message format matches protocol spec + +## Security Considerations + +- No security changes in this phase +- Ensure no sensitive data handling changes + +## Next Steps + +- [Phase 3](./phase-03-mvvm-refactor.md) - Create proper ViewModels with RelayCommand +- [Phase 1](./phase-01-split-mainwindow.md) - Can run in parallel if duplicate removal is simple \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-03-mvvm-refactor.md b/plans/260520-1520-system-design-refactoring/phase-03-mvvm-refactor.md new file mode 100644 index 0000000..ad7ae6f --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-03-mvvm-refactor.md @@ -0,0 +1,119 @@ +--- +title: "Phase 3 - MVVM Refactor" +description: "Create proper ViewModels with RelayCommand using CommunityToolkit.Mvvm" +status: pending +priority: P1 +effort: 4h +branch: feature/emoji-reactions +tags: [refactoring, mvvm, community-toolkit] +created: 2026-05-20 +--- + +# Phase 3: MVVM Refactor with RelayCommand + +Convert all button click handlers and UI logic to proper MVVM with CommunityToolkit.Mvvm. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) +- [Phase 1](./phase-01-split-mainwindow.md) +- [Phase 2](./phase-02-cleanup-duplicates.md) + +## Overview + +Current implementation uses code-behind event handlers (e.g., `SendButton_Click`, `EmojiPickerButton_Click`). Need to convert to `ICommand` pattern using `RelayCommand` from CommunityToolkit.Mvvm. + +## Key Insights + +- CommunityToolkit.Mvvm already in tech stack but not fully utilized +- `RelayCommand` handles CanExecute for button enable/disable +- `ObservableObject` provides `PropertyChanged` notification +- Convert all event handlers in MainWindow.xaml.cs to commands + +## Requirements + +### Functional +- Convert SendButton_Click to SendCommand +- Convert EmojiPickerButton_Click to ToggleEmojiPickerCommand +- Convert ReactionButton_Click to ShowReactionPopupCommand +- Convert InfoButton_Click to ShowSettingsCommand +- Convert Channel selection to SelectChannelCommand +- Convert Image thumbnail click to OpenLightboxCommand +- Convert File thumbnail click to OpenFileCommand + +### Non-Functional +- All existing keyboard shortcuts preserved +- Button enable/disable states work correctly + +## Architecture + +``` +MainViewModel (with CommunityToolkit.Mvvm) +├── [ObservableProperty] Username +├── [ObservableProperty] InputText +├── [ObservableProperty] IsEmojiPickerOpen +├── [RelayCommand] SendMessage +├── [RelayCommand] ToggleEmojiPicker +├── [RelayCommand] ShowReactionPopup +├── [RelayCommand] ShowSettings +├── [RelayCommand] SelectChannel +└── [RelayCommand] OpenLightbox +``` + +## Related Code Files + +### Files to Modify +- `ChatBox.Client/ViewModels/MainViewModel.cs` +- `ChatBox.Client/ViewModels/MessageListViewModel.cs` +- `ChatBox.Client/ViewModels/InputAreaViewModel.cs` +- `ChatBox.Client/MainWindow.xaml` - Update binding syntax + +### Files to Check +- Any Manager classes that handle button clicks + +## Implementation Steps + +1. **Check if CommunityToolkit.Mvvm is installed** in ChatBox.Client csproj +2. **Read existing ViewModel files** to understand current structure +3. **Update MainViewModel.cs** - Add ObservableProperty attributes and RelayCommand attributes +4. **Update MessageListViewModel.cs** - Add message selection commands +5. **Update InputAreaViewModel.cs** - Add send command with CanExecute +6. **Update MainWindow.xaml** - Change event handlers to Command bindings +7. **Remove event handlers** from MainWindow.xaml.cs +8. **Compile and verify** - Ensure commands work + +## Todo List + +- [ ] Verify CommunityToolkit.Mvvm package in ChatBox.Client +- [ ] Read existing ViewModels to understand structure +- [ ] Update MainViewModel with [ObservableProperty] and [RelayCommand] +- [ ] Update MessageListViewModel with selection commands +- [ ] Update InputAreaViewModel with send command +- [ ] Update MainWindow.xaml Command bindings +- [ ] Remove event handler code from MainWindow.xaml.cs +- [ ] Compile and verify all commands work + +## Success Criteria + +- All button clicks use RelayCommand +- Keyboard shortcuts work (Enter to send, Ctrl+V to paste) +- No event handlers in MainWindow.xaml.cs +- All buttons enable/disable based on CanExecute + +## Risk Assessment + +- **Risk**: CommunityToolkit.Mvvm version mismatch +- **Mitigation**: Check csproj for version, update if needed +- **Risk**: Complex commands with parameters +- **Mitigation**: Use CommandParameter binding in XAML + +## Security Considerations + +- No security changes in this phase +- Preserve existing input validation + +## Next Steps + +- [Phase 4](./phase-04-add-di-container.md) - Add DI container +- [Phase 5](./phase-05-create-views-controls.md) - Extract XAML controls \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-04-add-di-container.md b/plans/260520-1520-system-design-refactoring/phase-04-add-di-container.md new file mode 100644 index 0000000..a709b42 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-04-add-di-container.md @@ -0,0 +1,126 @@ +--- +title: "Phase 4 - Add DI Container" +description: "Add Microsoft.Extensions.DependencyInjection for service registration" +status: pending +priority: P2 +effort: 2h +branch: feature/emoji-reactions +tags: [refactoring, di, dependency-injection] +created: 2026-05-20 +--- + +# Phase 4: Add DI Container + +Add Microsoft.Extensions.DependencyInjection for loose coupling and testability. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) +- [Phase 3](./phase-03-mvvm-refactor.md) + +## Overview + +Services are currently created manually in MainWindow.xaml.cs. Adding DI container will: +1. Enable constructor injection +2. Improve testability (can substitute mocks) +3. Follow best practices for .NET applications +4. Reduce tight coupling between components + +## Key Insights + +- Services currently created as singletons in MainWindow constructor +- DI container will manage service lifetimes +- ViewModels can receive services via constructor +- Need to register: ChatClient, FileClient, Managers + +## Requirements + +### Functional +- Add Microsoft.Extensions.DependencyInjection NuGet package +- Register all services in DI container +- Update MainWindow to resolve services from container +- Update ViewModels to receive services via constructor + +### Non-Functional +- Preserve existing service lifetimes (singleton for most) +- No breaking changes to existing code paths + +## Architecture + +``` +App.xaml.cs +├── ConfigureServices() +│ ├── services.AddSingleton() +│ ├── services.AddSingleton() +│ ├── services.AddSingleton() +│ └── services.AddTransient() +└── serviceProvider (static) + +MainWindow.xaml.cs +├── MainViewModel vm (resolved from container) +└── DataContext = vm +``` + +## Related Code Files + +### Files to Modify +- `ChatBox.Client/App.xaml.cs` +- `ChatBox.Client/MainWindow.xaml.cs` +- `ChatBox.Client/ViewModels/MainViewModel.cs` +- `ChatBox.Client/ViewModels/MessageListViewModel.cs` + +### Files to Create +- `ChatBox.Client/Services/ServiceConfiguration.cs` (optional, for organization) + +## Implementation Steps + +1. **Add NuGet package** - Microsoft.Extensions.DependencyInjection +2. **Read App.xaml.cs** to understand current startup +3. **Create DI configuration** in App.xaml.cs +4. **Register ChatServer, FileServer** as singletons +5. **Register Managers** as singletons +6. **Register ViewModels** as transient +7. **Update MainWindow.xaml.cs** - Remove manual service creation +8. **Update ViewModels** - Add constructor parameters +9. **Compile and verify** + +## Todo List + +- [ ] Add Microsoft.Extensions.DependencyInjection NuGet package +- [ ] Read App.xaml.cs to understand startup +- [ ] Create ConfigureServices method in App.xaml.cs +- [ ] Register ChatServer (IChatServer) +- [ ] Register FileServer (IFileServer) +- [ ] Register ConnectionManager +- [ ] Register GalleryManager +- [ ] Register LightboxManager +- [ ] Register other Managers +- [ ] Register ViewModels as transient +- [ ] Update MainWindow.xaml.cs to resolve from container +- [ ] Update ViewModels with constructor injection +- [ ] Compile and verify + +## Success Criteria + +- All services resolved via DI container +- No manual `new SomeManager()` calls in code +- ViewModels receive services via constructor +- Application starts and works correctly + +## Risk Assessment + +- **Risk**: Breaking existing singleton behavior +- **Mitigation**: Register as singleton explicitly +- **Risk**: Circular dependency +- **Mitigation**: Restructure if needed, use interface abstractions + +## Security Considerations + +- No security changes in this phase +- Container itself is secure + +## Next Steps + +- [Phase 5](./phase-05-create-views-controls.md) - Extract XAML controls +- [Phase 6](./phase-06-refactor-core-services.md) - Split core service files \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-05-create-views-controls.md b/plans/260520-1520-system-design-refactoring/phase-05-create-views-controls.md new file mode 100644 index 0000000..a377aa3 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-05-create-views-controls.md @@ -0,0 +1,131 @@ +--- +title: "Phase 5 - Create Views Controls" +description: "Extract XAML user controls: MessageBubble, EmojiPicker, ReactionPopup" +status: pending +priority: P2 +effort: 6h +branch: feature/emoji-reactions +tags: [refactoring, xaml, user-controls] +created: 2026-05-20 +--- + +# Phase 5: Create Views/Controls + +Extract XAML into reusable user controls following the folder structure from system design. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) +- [Phase 4](./phase-04-add-di-container.md) + +## Overview + +Extract inline XAML into reusable controls: +1. MessageBubble - message display with avatar, reactions +2. UserAvatar - avatar with fallback initials +3. EmojiPicker - emoji selection grid popup +4. ReactionPopup - quick emoji reaction buttons + +## Key Insights + +- Current message items are defined inline in MessageList DataTemplate +- Emoji picker is a popup overlay defined in MainWindow +- Reaction buttons are inline in message template +- Extraction enables reuse and simplifies MainWindow.xaml + +## Requirements + +### Functional +- Create MessageBubble.xaml with sender, timestamp, content, reactions +- Create UserAvatar.xaml with initials fallback +- Create EmojiPicker.xaml with category tabs and emoji grid +- Create ReactionPopup.xaml with quick reaction buttons +- Create MessageList.xaml as composite component + +### Non-Functional +- Maintain exact same visual appearance +- All bindings work correctly +- No duplicate visual elements + +## Architecture + +``` +ChatBox.Client/Views/ +├── Controls/ +│ ├── MessageBubble.xaml + MessageBubble.xaml.cs +│ ├── UserAvatar.xaml + UserAvatar.xaml.cs +│ ├── EmojiPicker.xaml + EmojiPicker.xaml.cs +│ └── ReactionPopup.xaml + ReactionPopup.xaml.cs +├── Components/ +│ ├── MessageList.xaml + MessageList.xaml.cs +│ ├── ImageGallery.xaml + ImageGallery.xaml.cs +│ ├── FileGallery.xaml + FileGallery.xaml.cs +│ ├── OnlineUsersPanel.xaml + OnlineUsersPanel.xaml.cs +│ └── ChannelSidebar.xaml + ChannelSidebar.xaml.cs +└── Overlays/ + ├── LightboxOverlay.xaml + LightboxOverlay.xaml.cs + └── SettingsOverlay.xaml + SettingsOverlay.xaml.cs +``` + +## Related Code Files + +### Files to Create +- `ChatBox.Client/Views/Controls/MessageBubble.xaml` +- `ChatBox.Client/Views/Controls/MessageBubble.xaml.cs` +- `ChatBox.Client/Views/Controls/UserAvatar.xaml` +- `ChatBox.Client/Views/Controls/UserAvatar.xaml.cs` +- `ChatBox.Client/Views/Controls/EmojiPicker.xaml` +- `ChatBox.Client/Views/Controls/EmojiPicker.xaml.cs` +- `ChatBox.Client/Views/Controls/ReactionPopup.xaml` +- `ChatBox.Client/Views/Controls/ReactionPopup.xaml.cs` + +### Files to Modify +- `ChatBox.Client/MainWindow.xaml` - Use extracted controls +- `ChatBox.Client/MainWindow.xaml.cs` - Update event handlers to commands + +## Implementation Steps + +1. **Create folder structure** - Views/Controls, Views/Components, Views/Overlays +2. **Create UserAvatar.xaml** - Simplest control, start here +3. **Create MessageBubble.xaml** - Message display with bindings +4. **Create ReactionPopup.xaml** - Quick reaction overlay +5. **Create EmojiPicker.xaml** - Category tabs and emoji grid +6. **Update MainWindow.xaml** - Replace inline XAML with control references +7. **Update code-behind** - Convert to commands +8. **Compile and verify** - Visual inspection + +## Todo List + +- [ ] Create Views/Controls folder structure +- [ ] Create UserAvatar.xaml + code-behind +- [ ] Create MessageBubble.xaml + code-behind +- [ ] Create ReactionPopup.xaml + code-behind +- [ ] Create EmojiPicker.xaml + code-behind +- [ ] Create MessageList component +- [ ] Update MainWindow.xaml to use controls +- [ ] Compile and visual verify + +## Success Criteria + +- MainWindow.xaml simplified with control references +- All controls visually identical to previous inline version +- Bindings work correctly +- No duplicate XAML + +## Risk Assessment + +- **Risk**: Breaking existing bindings +- **Mitigation**: Test each control individually +- **Risk**: Style differences +- **Mitigation**: Copy exact styles from existing MainWindow.xaml + +## Security Considerations + +- No security changes in this phase +- All content display only + +## Next Steps + +- [Phase 6](./phase-06-refactor-core-services.md) - Split core service files +- Post-refactor: Add unit tests \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/phase-06-refactor-core-services.md b/plans/260520-1520-system-design-refactoring/phase-06-refactor-core-services.md new file mode 100644 index 0000000..d8e0346 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/phase-06-refactor-core-services.md @@ -0,0 +1,132 @@ +--- +title: "Phase 6 - Refactor Core Services" +description: "Split ChatService.cs and FileTransferService.cs into individual classes" +status: pending +priority: P3 +effort: 6h +branch: feature/emoji-reactions +tags: [refactoring, services, core] +created: 2026-05-20 +--- + +# Phase 6: Refactor Core Services + +Split large service files (containing 2+ classes each) into individual class files. + +## Context Links + +- [System Design](../system-design.md) +- [Plan Overview](../plan.md) +- [Phase 5](./phase-05-create-views-controls.md) + +## Overview + +Current service files: +- `ChatService.cs` contains `ChatServer` + `ChatClient` classes +- `FileTransferService.cs` contains `FileServer` + `FileClient` classes + +Each file violates the 200-line rule. Split into individual files per class. + +## Key Insights + +- Each class already has distinct responsibility +- ChatServer handles server-side TCP messaging +- ChatClient handles client-side TCP messaging +- FileServer handles file upload reception +- FileClient handles file download +- Split is straightforward file organization + +## Requirements + +### Functional +- Split ChatService.cs into ChatServer.cs and ChatClient.cs +- Split FileTransferService.cs into FileServer.cs and FileClient.cs +- Update using statements in all referencing files +- Preserve all existing functionality + +### Non-Functional +- No behavioral changes +- Same class public interfaces +- Same internal implementation + +## Architecture + +``` +LocalChat.Core/Services/ (Before) +├── ChatService.cs # Contains ChatServer + ChatClient (~500 lines) +└── FileTransferService.cs # Contains FileServer + FileClient (~500 lines) + +LocalChat.Core/Services/ (After) +├── ChatServer.cs # Single class (~250 lines) +├── ChatClient.cs # Single class (~250 lines) +├── FileServer.cs # Single class (~250 lines) +└── FileClient.cs # Single class (~250 lines) +``` + +## Related Code Files + +### Files to Delete +- `LocalChat.Core/Services/ChatService.cs` +- `LocalChat.Core/Services/FileTransferService.cs` + +### Files to Create +- `LocalChat.Core/Services/ChatServer.cs` +- `LocalChat.Core/Services/ChatClient.cs` +- `LocalChat.Core/Services/FileServer.cs` +- `LocalChat.Core/Services/FileClient.cs` + +### Files to Modify +- Any files using `using LocalChat.Core.Services;` +- ChatBox.Client (references ChatClient, FileClient) +- ChatBox.Server (references ChatServer, FileServer) + +## Implementation Steps + +1. **Read ChatService.cs** to understand ChatServer and ChatClient classes +2. **Read FileTransferService.cs** to understand FileServer and FileClient classes +3. **Create ChatServer.cs** - Extract ChatServer class to new file +4. **Create ChatClient.cs** - Extract ChatClient class to new file +5. **Create FileServer.cs** - Extract FileServer class to new file +6. **Create FileClient.cs** - Extract FileClient class to new file +7. **Search for all files using ChatService.cs and FileTransferService.cs** +8. **Update using statements** in all referencing files +9. **Delete original files** +10. **Compile and verify** + +## Todo List + +- [ ] Read ChatService.cs to identify class boundaries +- [ ] Read FileTransferService.cs to identify class boundaries +- [ ] Create ChatServer.cs +- [ ] Create ChatClient.cs +- [ ] Create FileServer.cs +- [ ] Create FileClient.cs +- [ ] Search for all usages of original files +- [ ] Update using statements in ChatBox.Client +- [ ] Update using statements in ChatBox.Server +- [ ] Delete ChatService.cs +- [ ] Delete FileTransferService.cs +- [ ] Compile and verify + +## Success Criteria + +- All services in individual files under 200 lines +- No compile errors from reference changes +- All functionality preserved + +## Risk Assessment + +- **Risk**: Breaking namespace or using statements +- **Mitigation**: Copy exact namespace, update using statements +- **Risk**: Partial class issues +- **Mitigation**: Verify no partial class usage + +## Security Considerations + +- No security changes in this phase +- Refactoring only + +## Next Steps + +- Post-refactor: Add unit tests +- Post-refactor: Consider adding integration tests with test container \ No newline at end of file diff --git a/plans/260520-1520-system-design-refactoring/plan.md b/plans/260520-1520-system-design-refactoring/plan.md new file mode 100644 index 0000000..bb86ed7 --- /dev/null +++ b/plans/260520-1520-system-design-refactoring/plan.md @@ -0,0 +1,46 @@ +--- +title: "System Design Refactoring" +description: "Refactor ChatBox project: split MainWindow, remove duplicates, implement proper MVVM, add DI container" +status: pending +priority: P1 +effort: 24h +branch: feature/emoji-reactions +tags: [refactoring, mvvm, architecture] +created: 2026-05-20 +--- + +# System Design Refactoring Plan + +Split monolithic MainWindow.xaml.cs into proper MVVM architecture with CommunityToolkit.Mvvm. + +## Phases + +| Phase | Status | Description | +|-------|--------|-------------| +| [phase-01-split-mainwindow.md](./phase-01-split-mainwindow.md) | pending | Split MainWindow.xaml.cs into MainViewModel and supporting ViewModels | +| [phase-02-cleanup-duplicates.md](./phase-02-cleanup-duplicates.md) | pending | Remove duplicate ChatMessage classes, consolidate | +| [phase-03-mvvm-refactor.md](./phase-03-mvvm-refactor.md) | pending | Create proper ViewModels with RelayCommand | +| [phase-04-add-di-container.md](./phase-04-add-di-container.md) | pending | Add Microsoft.Extensions.DependencyInjection | +| [phase-05-create-views-controls.md](./phase-05-create-views-controls.md) | pending | Extract XAML user controls (MessageBubble, EmojiPicker, etc.) | +| [phase-06-refactor-core-services.md](./phase-06-refactor-core-services.md) | pending | Split large service files into individual classes | + +## Key Dependencies + +- Phase 1 → Phase 3 (ViewModel extraction) +- Phase 2 → Phase 3 (consolidated models) +- Phase 3 → Phase 4 (DI wiring) +- Phase 4 → Phase 5 (DI-based view construction) + +## Success Criteria + +- MainWindow.xaml.cs under 200 lines +- No duplicate model classes +- All UI logic uses RelayCommand pattern +- All services registered in DI container +- All XAML controls extracted to Views/Controls/ + +## Notes + +- Keep existing functionality working after each phase +- Commit after each phase for easy rollback +- Use CommunityToolkit.Mvvm for ObservableObject and RelayCommand \ No newline at end of file diff --git a/plans/260520-1652-image-file-draft-upload/phase-01-file-upload-draft.md b/plans/260520-1652-image-file-draft-upload/phase-01-file-upload-draft.md new file mode 100644 index 0000000..771df14 --- /dev/null +++ b/plans/260520-1652-image-file-draft-upload/phase-01-file-upload-draft.md @@ -0,0 +1,113 @@ +--- +title: "Phase 1: File Upload Draft Implementation" +description: "Modify upload code path to create draft items first instead of uploading immediately" +status: completed +priority: P1 +created: 2026-05-20 +--- + +# Phase 1: File Upload Draft Implementation + +## Context Links +- Parent: [plan.md](../plan.md) +- Scout report: `scout/scout-01.md` (synthesized) + +## Overview + +**Priority:** P1 | **Status:** Pending + +Modify the upload flow so files/images staged via + button, drag-drop, or paste create draft items first. User presses Enter to actually upload. This mirrors the existing paste draft behavior but for all upload methods. + +## Key Insights + +- `UploadFileAsync()` (lines 750-794) uploads immediately +- `HandleImagePaste()` (lines 1369-1425) correctly creates draft with `IsDraft=true` +- Need to create a `CreateDraftFileMessage()` method that creates the message in draft state (like paste) +- Both `BtnUpload_Click` and `Window_Drop` should call this draft method instead of `UploadFileAsync` +- `SendPendingImages()` (lines 1427-1454) handles the actual upload when Enter is pressed + +## Requirements + +### Functional +- When user clicks + button or drags file → create draft item in chat (purple dashed border) +- When user presses Enter → `SendPendingImages()` uploads all drafts +- Max 10 draft items enforced on all paths + +### Non-functional +- Same UX for + button, drag-drop, and paste +- No change to existing server protocol + +## Architecture + +**Current flow (broken):** +``` ++ Button / Drag-Drop → UploadFileAsync() → IsTransferring=true → upload starts immediately +``` + +**New flow (target):** +``` ++ Button / Drag-Drop → CreateDraftFileMessage() → IsDraft=true → appears in chat +Enter pressed → SendPendingImages() → IsDraft=false → IsTransferring=true → upload starts +``` + +## Related Code Files + +**Modify:** +- `ChatBox.Client/MainWindow.xaml.cs` + - `BtnUpload_Click` (796-803): Call `CreateDraftFileMessage()` instead of `UploadFileAsync()` + - `Window_Drop` (818-834): Call `CreateDraftFileMessage()` instead of `UploadFileAsync()` + - Add `CreateDraftFileMessage()` method (new) + - `SendPendingImages()` (1427-1454): Keep as-is - handles draft→upload transition + +## Implementation Steps + +1. **Add `CreateDraftFileMessage()` method** to `MainWindow.xaml.cs`: + - Takes `filePath` string parameter + - Checks `_pendingImages.Count >= 10` + - Creates `ChatMessage` with `IsDraft=true, IsTransferring=false` + - Adds to `_pendingImages` and `_allMessages` + - Calls `RefreshMessageList()` + - Returns the created `ChatMessage` or null if at limit + +2. **Modify `BtnUpload_Click`**: + - Remove `await UploadFileAsync(dialog.FileName)` + - Call `CreateDraftFileMessage(dialog.FileName)` instead + +3. **Modify `Window_Drop`**: + - Remove `await UploadFileAsync(file)` call + - Call `CreateDraftFileMessage(file)` instead + - Keep the async/await structure but change method called + +4. **Verify `HandleImagePaste()` already does the right thing** - it creates draft, nothing to change + +## Todo List + +- [ ] Add `CreateDraftFileMessage()` method +- [ ] Update `BtnUpload_Click` to use draft method +- [ ] Update `Window_Drop` to use draft method +- [ ] Test: + button creates draft with purple border +- [ ] Test: drag-drop creates draft with purple border +- [ ] Test: Enter sends all drafts + +## Success Criteria + +1. Files dropped or selected via + button appear in chat with purple dashed border (IsDraft=true) before Enter +2. Pressing Enter uploads all pending drafts and shows ProgressBar loading animation +3. Cannot stage more than 10 images (existing check works for all paths) +4. Existing paste (Ctrl+V) behavior unchanged + +## Risk Assessment + +- **Risk:** `_pendingImages.Clear()` in `SendPendingImages()` might cause issues if multiple upload paths add to it +- **Mitigation:** `_pendingImages` list is the correct staging area, clear is intentional before re-uploading +- **Risk:** `_currentTransferMessage` tracking in `UploadFileAsync` not used in draft path +- **Mitigation:** Draft path doesn't need `_currentTransferMessage` since upload happens in `SendPendingImages()` + +## Security Considerations + +- File validation already exists (checks `File.Exists`) +- No new security concerns - same file handling as before + +## Next Steps + +- Phase 2: Verify ProgressBar animation fires correctly during actual upload \ No newline at end of file diff --git a/plans/260520-1652-image-file-draft-upload/phase-02-loading-animation.md b/plans/260520-1652-image-file-draft-upload/phase-02-loading-animation.md new file mode 100644 index 0000000..29b655e --- /dev/null +++ b/plans/260520-1652-image-file-draft-upload/phase-02-loading-animation.md @@ -0,0 +1,84 @@ +--- +title: "Phase 2: Loading Animation Verification" +description: "Verify ProgressBar shows during upload - fix if needed" +status: completed +priority: P1 +created: 2026-05-20 +--- + +# Phase 2: Loading Animation Verification + +## Context Links +- Parent: [plan.md](../plan.md) +- Phase 1: [phase-01-file-upload-draft.md](./phase-01-file-upload-draft.md) + +## Overview + +**Priority:** P1 | **Status:** Pending + +Verify the ProgressBar loading animation shows correctly during file upload. The ProgressBar bindings exist in XAML but we need to confirm they update during `SendPendingImages()`. + +## Key Insights + +- `ChatMessage.TransferProgress` property exists (lines 29-34 in ChatMessage.cs) +- `ChatMessage.IsTransferring` property exists (lines 36-41) +- XAML ProgressBar bindings at lines 1061, 1078, 1217, 1293 bind to these properties +- `FileClient.UploadFileAsync()` fires `OnUploadProgress` event with percentage +- `UpdateProgress()` handler at lines 872-880 updates UI thread + +## Architecture + +**Current flow in SendPendingImages:** +```csharp +msg.IsDraft = false; +msg.IsTransferring = true; // Should show ProgressBar +RefreshMessageList(); + +await _fileClient.UploadFileAsync(...); // Progress events fire + +msg.IsTransferring = false; // Should hide ProgressBar +``` + +The flow looks correct but we need to verify: +1. `UpdateProgress()` handler is wired to `OnUploadProgress` event +2. `TransferProgress` property setter calls `PropertyChanged` for UI binding + +## Related Code Files + +**Verify/Modify:** +- `LocalChat.Core/Services/FileTransferService.cs` - lines 93-135 (UploadFileAsync) +- `ChatBox.Client/ViewModels/ChatMessage.cs` - TransferProgress, IsTransferring properties +- `ChatBox.Client/MainWindow.xaml.cs` - UpdateProgress handler, event subscription + +## Implementation Steps + +1. **Verify event subscription**: Check if `OnUploadProgress` event is connected to `UpdateProgress` handler in `MainWindow.xaml.cs` + - Look for `_fileClient.OnUploadProgress += UpdateProgress` or similar + +2. **Verify PropertyChanged**: Check `ChatMessage.cs` that `TransferProgress` setter calls `OnPropertyChanged(nameof(TransferProgress))` + +3. **If not wired**: Add event subscription in `MainWindow.xaml.cs` initialization + +4. **Test**: Upload a file and verify ProgressBar animates from 0-100% + +## Todo List + +- [ ] Check event wiring for upload progress +- [ ] Check TransferProgress PropertyChanged +- [ ] Fix any broken wiring +- [ ] Test ProgressBar animation during upload + +## Success Criteria + +1. When upload starts, ProgressBar appears and animates 0→100% +2. ProgressBar disappears when upload completes +3. Animation is smooth (updates at least every few percent) + +## Risk Assessment + +- **Risk:** If event wiring is missing, progress won't update +- **Mitigation:** Simple wiring fix if needed + +## Next Steps + +- Phase 3: Add remove (X) button to draft items \ No newline at end of file diff --git a/plans/260520-1652-image-file-draft-upload/phase-03-remove-button.md b/plans/260520-1652-image-file-draft-upload/phase-03-remove-button.md new file mode 100644 index 0000000..d0c2d33 --- /dev/null +++ b/plans/260520-1652-image-file-draft-upload/phase-03-remove-button.md @@ -0,0 +1,77 @@ +--- +title: "Phase 3: Remove Button for Draft Items" +description: "Add X button to draft images/files so user can remove before sending" +status: completed +priority: P1 +created: 2026-05-20 +--- + +# Phase 3: Remove Button for Draft Items + +## Context Links +- Parent: [plan.md](../plan.md) + +## Overview + +**Priority:** P1 | **Status:** Pending + +Add a remove (X) button to draft images/files so users can remove them before pressing Enter to send. + +## Key Insights + +- `BtnRemovePending_Click` handler exists at lines 1561-1569 in `MainWindow.xaml.cs` +- No XAML button is currently bound to this handler (grep returned no matches) +- Need to add an X button overlay on draft image/file items + +## Architecture + +**Current state:** +- Draft images show with purple dashed border (via DataTrigger at lines 1137-1142) +- No visible way to remove them before sending + +**Target state:** +- X button appears in top-right corner of draft items +- Clicking removes from `_pendingImages` and `_allMessages` + +## Related Code Files + +**Modify:** +- `ChatBox.Client/MainWindow.xaml` - Add remove button to draft image template +- `ChatBox.Client/MainWindow.xaml.cs` - `BtnRemovePending_Click` already exists + +## Implementation Steps + +1. **Add remove Button to ImageEmbedBorder** template: + - Position absolute in top-right corner + - Only visible when `IsDraft=True` + - Style: small circular button with X or trash icon + - Bind `Click="BtnRemovePending_Click"` + - Bind `Tag="{Binding}"` to pass the message to handler + +2. **Add remove Button to File attachment template**: + - Similar approach - X button in top-right of file card + - Only visible when `IsDraft=True` + +3. **Test**: Draft items show X button, clicking removes from chat + +## Todo List + +- [ ] Add X button to draft image template (ImageEmbedBorder) +- [ ] Add X button to draft file template (FileAttachmentBorder) +- [ ] Test remove button on draft images +- [ ] Test remove button on draft files + +## Success Criteria + +1. Draft images show X button in top-right corner +2. Clicking X removes image from draft list +3. X button only visible for draft items (IsDraft=True) +4. Works for both images and file attachments + +## Risk Assessment + +- None identified - straightforward button binding + +## Next Steps + +- Phase 4: Testing & verification \ No newline at end of file diff --git a/plans/260520-1652-image-file-draft-upload/phase-04-testing.md b/plans/260520-1652-image-file-draft-upload/phase-04-testing.md new file mode 100644 index 0000000..eaa5cea --- /dev/null +++ b/plans/260520-1652-image-file-draft-upload/phase-04-testing.md @@ -0,0 +1,66 @@ +--- +title: "Phase 4: Testing & Verification" +description: "Test all draft upload flows and loading animation" +status: completed +priority: P1 +created: 2026-05-20 +--- + +# Phase 4: Testing & Verification + +## Context Links +- Parent: [plan.md](../plan.md) +- Phase 1-3 complete before this phase + +## Overview + +**Priority:** P1 | **Status:** Pending + +Comprehensive testing of all draft upload flows. + +## Implementation Steps + +### Test Cases + +1. **Ctrl+V Paste Image** + - [ ] Image appears in chat with purple dashed border + - [ ] X button removes image + - [ ] Enter uploads and shows ProgressBar + +2. **+ Button Upload (File dialog)** + - [ ] File appears in chat with purple dashed border (before Enter) + - [ ] X button removes file + - [ ] Enter uploads and shows ProgressBar + +3. **Drag-Drop Upload** + - [ ] File appears in chat with purple dashed border (before Enter) + - [ ] X button removes file + - [ ] Enter uploads and shows ProgressBar + +4. **Multiple Files** + - [ ] Up to 10 files can be staged + - [ ] 11th file shows "Maximum 10 images" message + +5. **Loading Animation** + - [ ] ProgressBar shows during upload (0-100%) + - [ ] ProgressBar hides when upload completes + +6. **Mixed Content** + - [ ] Text + images sent together on Enter + - [ ] Each file gets its own draft item + +## Todo List + +- [ ] Test Ctrl+V paste +- [ ] Test + button upload +- [ ] Test drag-drop +- [ ] Test 10 file limit +- [ ] Test ProgressBar animation +- [ ] Test mixed text + images + +## Success Criteria + +All test cases pass. User can: +- Stage up to 10 images/files with visual feedback +- Remove any staged item before sending +- Press Enter to upload all with loading animation \ No newline at end of file diff --git a/plans/260520-1652-image-file-draft-upload/plan.md b/plans/260520-1652-image-file-draft-upload/plan.md new file mode 100644 index 0000000..527a674 --- /dev/null +++ b/plans/260520-1652-image-file-draft-upload/plan.md @@ -0,0 +1,37 @@ +--- +title: "Image/File Draft Upload with Loading Animation" +description: "When uploading or Ctrl+V, images/files should draft in chat box for review before sending (Enter). Add loading animation during upload. Support up to 10 images." +status: done +priority: P1 +effort: 6h +branch: feature/emoji-reactions +tags: [image-upload, draft, loading-animation, wpf] +created: 2026-05-20 +--- + +# Feature Implementation Plan + +## Overview + +Implement draft behavior for image/file uploads: staged in chat with purple dashed border, removable before Enter. Add loading animation during upload. Max 10 images. + +## Phases + +| # | Phase | Status | Effort | Link | +|---|-------|--------|--------|------| +| 1 | Fix file upload to draft before sending | Pending | 2h | [phase-01](./phase-01-file-upload-draft.md) | +| 2 | Add loading animation during upload | Pending | 2h | [phase-02](./phase-02-loading-animation.md) | +| 3 | Wire up remove button on draft items | Pending | 1h | [phase-03](./phase-03-remove-button.md) | +| 4 | Testing & verification | Pending | 1h | [phase-04](./phase-04-testing.md) | + +## Dependencies + +- Existing: `_pendingImages` list, `IsDraft` flag, `IsTransferring` flag, `TransferProgress` property +- No new dependencies needed + +## Key Problems Identified + +1. **Upload via + button and drag-drop** directly calls `UploadFileAsync()` which starts upload immediately without drafting +2. **Paste (Ctrl+V)** correctly creates draft with `IsDraft=true` but `BtnRemovePending_Click` has no XAML binding +3. **Loading animation** exists (ProgressBar bound to `IsTransferring`/`TransferProgress`) but not triggered because upload starts immediately +4. **Remove button** `BtnRemovePending_Click` exists in code-behind but no XAML button binding found \ No newline at end of file diff --git a/plans/260520-2300-staging-panel-reposition/plan.md b/plans/260520-2300-staging-panel-reposition/plan.md new file mode 100644 index 0000000..0bec136 --- /dev/null +++ b/plans/260520-2300-staging-panel-reposition/plan.md @@ -0,0 +1,97 @@ +# Plan: Reposition Draft Staging Panel Above Input Box + +## Summary +Move the draft staging panel from its current floating/scattered position to a dedicated slot **right above the message input box**, as a persistent horizontal strip. This makes staged images/files visible and organized, directly adjacent to where the user types. + +## Context +- **Parent:** `260520-1652-image-file-draft-upload/plan.md` (image/file draft upload feature) +- **Issue:** After the recent fix (removing inline chat insertion), pasted/dragged images only go to `DraftStagingPanel`. But the panel is not clearly positioned near the input area - user wants it "near chat, not random panel." + +## Current Layout (Grid.Row assignments in pnlChat) + +| Row | Height | Content | +|-----|--------|---------| +| 0 | 50 | Chat Header | +| 1 | * | Message List / Galleries | +| 2 | Auto | [EMPTY - was staging panel] | +| 3 | Auto | Draft Staging Panel (currently scattered) | +| 4 | Auto | Input Area | + +**Problem:** `Grid.Row="3"` for staging panel - no explicit positioning makes it appear "random." + +## Target Layout + +| Row | Height | Content | +|-----|--------|---------| +| 0 | 50 | Chat Header | +| 1 | * | Message List / Galleries | +| 2 | Auto | **Draft Staging Panel** (repositioned here, always visible when items exist) | +| 3 | Auto | Input Area (tight margin above) | + +**Key change:** Swap staging panel to `Grid.Row="2"`, adjust margins so it sits directly above input area. + +## Requirements +1. Staging panel appears **immediately above the input box** when items are staged +2. Panel is a **horizontal strip** of thumbnails (same 256x256 size) +3. Small, unobtrusive - doesn't block the chat messages +4. When collapsed (no pending images), takes **zero vertical space** +5. When expanded, pushes input box down naturally + +## Architecture + +### Current Row Definitions: +```xml + + + + + + + +``` + +### Target Row Definitions: +```xml + + + + + + +``` + +**Remove one empty row (Row 2 currently empty).** + +### XAML Changes: +1. Move `DraftStagingPanel` from `Grid.Row="3"` to `Grid.Row="2"` +2. Remove the empty `RowDefinition Height="Auto"` at row 2 +3. Adjust margins: panel's top margin from ~10 to something like 8-10px +4. The input area `Grid.Row="4"` becomes `Grid.Row="3"` +5. Update `UpdateDraftPanelVisibility()` to properly show/hide with correct row behavior + +## Implementation Steps + +### Step 1: Edit MainWindow.xaml +- Remove the empty `RowDefinition` between messages and staging panel +- Move `DraftStagingPanel` to `Grid.Row="2"` (was row 3) +- Shift input area `Grid.Row` from 4 to 3 +- Adjust panel margins to be tight against input area + +### Step 2: Verify Code-behind +- `UpdateDraftPanelVisibility()` uses `Visibility.Visible/Collapsed` - already correct +- No code changes needed in `.cs` for positioning + +## Success Criteria +1. Draft staging panel appears as a **horizontal strip directly above** the message input box +2. Panel is positioned at `Grid.Row="2"` in the chat panel's grid +3. When no pending images: panel hidden, input box at normal position +4. When pending images exist: panel visible, input box pushed down +5. Thumbnail size remains 256x256px + +## Risk Assessment +- **Low risk** - pure XAML layout adjustment +- No logic changes, no interaction with upload/send flow +- Should not affect any existing functionality + +## Unresolved +None - layout change is straightforward. \ No newline at end of file diff --git a/plans/260520-2315-reduce-max-draft-images/plan.md b/plans/260520-2315-reduce-max-draft-images/plan.md new file mode 100644 index 0000000..2b6ea28 --- /dev/null +++ b/plans/260520-2315-reduce-max-draft-images/plan.md @@ -0,0 +1,42 @@ +# Plan: Reduce Max Draft Images & Thumbnail Size + +## Summary +1. Change max staged images from **10 → 5** +2. Reduce staging panel thumbnail size from **256x256 → 128x128** so multiple fit horizontally + +## Context +- User: staging panel shows only one image, needs to show more +- Solution: shrink thumbnails from 256px to 128px so more fit side-by-side +- Also reducing max limit to 5 per user request + +## Context +- User wants to limit staged images to max 5 (currently 10) +- Simple one-line change in two places + +## Thumbnail Size Change (MainWindow.xaml) + +Lines ~1368-1370: +```xml + + + + + + + +``` + +## Changes Summary + +| File | Line | Change | +|------|------|--------| +| `MainWindow.xaml.cs` | ~1374 | `Count >= 10` → `Count >= 5` | +| `MainWindow.xaml.cs` | ~1431 | `Count >= 10` → `Count >= 5` | +| `MainWindow.xaml` | ~1368 | `Width="256" Height="256"` → `128x128` | +| `MainWindow.xaml` | ~1370 | `Width="256" Height="256"` → `128x128` | + +## Success Criteria +1. Max 5 images staged at once +2. Thumbnails 128x128px (smaller, more fit horizontally) +3. Scroll appears if more than can fit in panel width +4. Build succeeds \ No newline at end of file