docs: Update documentation for 145 files

Generated by AurionDocs
Job ID: c99fff50-67a3-4294-b4df-3e73f4f12de9
Source commit: 4dcb480
This commit is contained in:
Hue
2026-07-23 08:10:35 +02:00
parent 4dcb480d1d
commit f8f4e03ddd
145 changed files with 22779 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,124 @@
# ClientConfig.cs
> **Source:** `src/EchoHub.Client/Config/ClientConfig.cs`
## Contents
- [AccountPreset](#accountpreset)
- [ClientConfig](#clientconfig)
- [NotificationConfig](#notificationconfig)
- [SavedServer](#savedserver)
---
## AccountPreset
> **File:** `src/EchoHub.Client/Config/ClientConfig.cs`
> **Kind:** class
```csharp
public class AccountPreset
```
AccountPreset is a lightweight data container that groups three optional account identity properties—DisplayName, Bio, and NicknameColor—so callers can apply or persist a predefined persona for an account. It is intended for use in client configuration (ClientConfig.cs), enabling a consistent, reusable identity profile to be attached to account-related logic.
## Remarks
AccountPreset exists to keep related identity attributes together, reducing the surface area of APIs that need to accept or propagate persona data. It aligns with a configuration/templating pattern in the client, making it easier to serialize, store, and reuse account personas across components that render or modify user identity.
## Notes
- All properties are nullable; callers must define default behavior when a property is null (e.g., preserve existing values or apply a fallback).
- Null-valued properties may be serialized depending on the chosen serializer; configure to ignore nulls if you prefer a clean configuration payload.
- There is no validation here; enforce constraints instead in the surrounding configuration or UI logic.
---
## ClientConfig
> **File:** `src/EchoHub.Client/Config/ClientConfig.cs`
> **Kind:** class
```csharp
public class ClientConfig
```
ClientConfig is the central container for a user's preferences and runtime state in the EchoHub client. It aggregates saved servers, the active account preset, the UI theme, notification settings, and attachment-handling options such as the download path and ASCII-rendering size.
## Remarks
It acts as a single source of truth for components that configure server connectivity, UI theming, and how attachments are stored and rendered. Centralizing defaults and user-specific values reduces duplication and helps ensure consistent behavior across sessions and test environments.
## Example
```csharp
var config = new ClientConfig
{
SavedServers = new List<SavedServer>
{
new SavedServer { Name = "Work", Url = "https://work.example", RememberMe = true }
},
DownloadPath = @"C:\Downloads",
DefaultAsciiSize = "m"
};
```
## Notes
- DownloadPath being null means attachments and saved images go to the OS Downloads folder. Ensure the application has write permissions to that location when relying on the default.
- DefaultAsciiSize accepts "s" (40×40), "m" (80×80), or "l" (120×120). This size applies to copy-paste/drag-drop attachments that do not carry a per-file size flag.
---
## NotificationConfig
> **File:** `src/EchoHub.Client/Config/ClientConfig.cs`
> **Kind:** class
```csharp
public class NotificationConfig
```
NotificationConfig is a lightweight data container used by the EchoHub client to express how notifications should behave. It encapsulates three related knobs: Enabled, Volume, and SoundFile. Developers instantiate this class to configure or override the client's notification behavior when wiring up configuration (for example, within ClientConfig) or when configuring the notifier component. The defaults indicate that notifications are enabled by default, a modest default volume, and no custom sound file unless specified.
## Remarks
By grouping notification-related settings into a single object, NotificationConfig reduces coupling between components that render or play notification sounds and the rest of the configuration. It also provides a clean extension point: new knobs can be added in the future without scattering settings across call sites, since a single configuration object can be passed around.
## Example
```csharp
var config = new NotificationConfig
{
Enabled = true,
Volume = 40,
SoundFile = "assets/notify.wav"
};
```
## Notes
- Volume is stored as a byte (0255). If your UI operates in a 0100 range, map or clamp values appropriately before consumption.
- SoundFile is nullable; when it is null, the consumer should handle the absence of a custom sound (e.g., fall back to a default sound or skip audible notification based on the environment).
---
## SavedServer
> **File:** `src/EchoHub.Client/Config/ClientConfig.cs`
> **Kind:** class
```csharp
public class SavedServer
```
SavedServer is a client-side representation of a per-server configuration and its associated local state for the EchoHub client. It stores credentials and connection details (Name, Url, Username, RefreshToken), a RememberMe flag, and the last connection timestamp (LastConnected). It also holds per-channel state that remains on the client: ChannelKeys (end-to-end encrypted keys cached per channel), LeftChannels (channels the user explicitly left), and LastReadMessages (per-channel read markers). These keys live only on the user's machine; the server never sees them.
## Remarks
SavedServer acts as the single source of truth for a user's relationship to a particular server within the client. By keeping ChannelKeys and LastReadMessages client-side, the app can decrypt and present channel content and maintain read state even after restarts, without leaking sensitive information to the server. LeftChannels honors user intent by preventing auto-joining of channels the user has consciously left, until they rejoin. This abstraction fits alongside other per-server configuration objects and collates server identity, credentials, and per-channel metadata for efficient session restore and UX.
## Notes
- Sensitive data such as RefreshToken and ChannelKeys should be stored securely at rest; the server never holds these values.
- These collections are mutable; ensure proper synchronization if accessed from multiple threads to avoid data races or inconsistent state.
---
@@ -0,0 +1,21 @@
# ConfigManager
> **File:** `src/EchoHub.Client/Config/ConfigManager.cs`
> **Kind:** class
```csharp
public static class ConfigManager
```
ConfigManager provides a thread-safe, single-point API for loading and persisting the client's configuration to disk. Use Load to read the current ClientConfig and Save/SaveServer/RemoveServer to apply changes from the UI or background tasks (for example, after token refreshes or updating saved servers).
## Remarks
ConfigManager stores the configuration in a JSON file named config.json inside a per-user directory (.echohub) under the current user's profile. All file I/O is serialized with a private lock (FileLock) to prevent concurrent access from UI threads and background tasks. When you call SaveServer, the code locates an existing SavedServer by URL (case-insensitive) and updates it, or appends a new one if none exists; RemoveServer deletes entries by URL. The design uses best-effort error handling—exceptions are swallowed to avoid disrupting the app—but this means persistence failures are not surfaced to callers unless they implement their own checks.
## Notes
- Persistence operations swallow all exceptions, making failures non-fatal but potentially leading to invisible data loss.
- SavedServers are deduplicated by URL using a case-insensitive comparison; updating an existing URL won't create a duplicate.
- ConfigDir uses Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); on systems where a user profile is unavailable or access is restricted, initialization may fall back to a default path.
@@ -0,0 +1,15 @@
# Program
> **File:** `src/EchoHub.Client/Program.cs`
> **Kind:** file
The Program file serves as the application's entry point and startup bootstrap for the EchoHub client. It coordinates early startup tasks such as rollback handling, permission checks, configuration provisioning, logging setup, PATH preparation, post-update cleanup, and UI initialization before handing control to the main orchestrator and theme system.
## Remarks
It functions as a central bootstrap that hides cross-cutting concerns from downstream components. By coordinating UpdateBackupService for rollback support, PathSetup for PATH hygiene, and ThemeManager for theming, it decouples startup sequencing from the rest of the application and ensures the runtime begins in a well-defined state.
## Notes
- Rollback path exits the process after attempting a restore; normal startup does not proceed.
- If appsettings.json is missing, the code seeds it from an embedded example; if the resource isn't available, startup continues with defaults.
- Several operations are best-effort and exceptions are swallowed to avoid stopping startup (e.g., Unix permissions adjustments, cleanup of a leftover .old executable).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
# AsyncRunner
> **File:** `src/EchoHub.Client/Services/AsyncRunner.cs`
> **Kind:** class
```csharp
public static class AsyncRunner
```
Eliminates repeated Task.Run/try/catch/app.Invoke(ShowError) boilerplate by consolidating the common pattern of running background work and surfacing errors to the UI. It runs the provided async work on a background thread and routes any exceptions to the UI thread for user notification.
## Remarks
AsyncRunner encapsulates a cross-cutting concern: performing asynchronous work without blocking the UI and centralizing error reporting. It uses Task.Run to execute work off the calling thread and app.Invoke to marshal the error surface back to the UI. When an exception occurs, it logs the failure with the provided context (logContext if supplied, otherwise errorPrefix) and shows a UI message using showError prefixed by errorPrefix. Because Run is fire-and-forget (it returns void), callers should not rely on it for completion or exception propagation; choose a different pattern if you need to observe results.
## Notes
- This method is fire-and-forget; exceptions are caught and surfaced but not propagated to the caller.
- The UI update and logging rely on the provided IApplication and showError delegate; ensure they are safe to call from a background thread; app.Invoke is used to marshal to the UI thread.
@@ -0,0 +1,24 @@
# AudioPlaybackService
> **File:** `src/EchoHub.Client/Services/AudioPlaybackService.cs`
> **Kind:** class
```csharp
public class AudioPlaybackService
```
AudioPlaybackService is a thread-safe wrapper around an underlying audio player that exposes asynchronous playback controls and a finished event surface. Use it when you need serialized access to play, pause, resume, or stop audio and a consistent event interface without managing locks and state machines yourself.
## Remarks
To prevent concurrent calls from interfering with playback state, the class serializes all operations using a SemaphoreSlim. When PlayAsync is invoked while something is already playing, it stops the current track before starting the new file; PauseAsync, ResumeAsync, and StopAsync perform their actions only when appropriate states are detected. The PlaybackFinished event is forwarded from the internal player, so callers can react to completion without depending on the concrete implementation of the _player. Exceptions raised by the underlying player are caught and logged with a warning, ensuring playback issues do not crash the application.
## Example
```csharp
// Example usage
var audio = new AudioPlaybackService();
await audio.PlayAsync("path/to/file.mp3");
```
## Notes
- This wrapper serializes calls to avoid race conditions; however, it is not cancellation-aware. If you need to cancel an in-flight operation, extend the class with cancellation support or a dedicated cancellation mechanism.
@@ -0,0 +1,33 @@
# AvatarHelper
> **File:** `src/EchoHub.Client/Services/AvatarHelper.cs`
> **Kind:** class
```csharp
internal static class AvatarHelper
```
AvatarHelper centralizes the shared logic for uploading avatars by accepting either a local file path or an HTTP(S) URL, resolving the input to a stream, and uploading it via ApiClient.UploadAvatarAsync. It returns the ASCII art response from the server, providing a straightforward way to obtain the server-side representation of the uploaded avatar without duplicating local-file or network-handling code.
## Remarks
By supporting both local and remote sources behind a single UploadAsync entry point, AvatarHelper hides the mechanics of data retrieval and stream management from call sites and ensures consistent disposal of the stream. The actual upload is delegated to ApiClient, keeping concerns separated between data acquisition and server interaction. The class is internal, reinforcing its role as a reusable utility within the client layer rather than a public API.
The method propagates errors from file access, HTTP fetch, or the server upload to the caller, which is appropriate for a small, focused helper that prioritizes simplicity over internal retries or resilience policies.
## Example
```csharp
// Example usage within the same assembly
var client = new ApiClient("https://api.example.org");
string? artFromFile = await AvatarHelper.UploadAsync(client, @"C:\avatars\user.png");
string? artFromUrl = await AvatarHelper.UploadAsync(client, "https://example.org/avatars/user.png");
```
## Notes
- Creating a new HttpClient per invocation can lead to socket exhaustion in high-throughput scenarios; consider reusing a shared HttpClient instance or using HttpClientFactory in production code.
- If the local path does not exist, a FileNotFoundException is thrown.
- When targeting a URL, if the URL's file name is missing or lacks an extension, the code defaults to using avatar.png as the upload file name.
- Exceptions from the HTTP request or the server upload propagate to the caller; there is no retry logic within this helper.
@@ -0,0 +1,35 @@
# ClientEncryptionService
> **File:** `src/EchoHub.Client/Services/ClientEncryptionService.cs`
> **Kind:** class
```csharp
public sealed class ClientEncryptionService : IMessageEncryptionService
```
ClientEncryptionService provides client-side encryption for messages by applying AES-256-GCM using a key supplied by the server. It mirrors the servers encryption format so messages are encrypted end-to-end between client and server. When no key has been set, Encrypt is a no-op and returns the plaintext to preserve compatibility with unauthenticated flows; once initialized, Encrypt produces a prefixed, base64-encoded payload containing the nonce and ciphertext+tag, and Decrypt reverses this process. If decryption fails due to a missing or mismatched key or corrupted data, a sentinel message is returned to indicate the failure and prompt re-authentication to refresh the key.
## Remarks
This abstraction isolates cryptography behind a single, testable service that can be swapped or disabled without changing business logic. It enforces a clear security boundary: encryption only happens after a server-provided key is loaded, reducing the risk of leaking plaintext. The pre-key pass-through behavior preserves compatibility with existing flows during login or in environments where the key has not yet been fetched.
## Example
```csharp
// Example: encrypt and decrypt with a server-provided key
var client = new ClientEncryptionService();
// Create a 32-byte key for demonstration (replace with real server-provided key)
var keyBytes = new byte[32];
var base64Key = Convert.ToBase64String(keyBytes);
client.SetKey(base64Key);
string plaintext = "Secret message";
string encrypted = client.Encrypt(plaintext);
string decrypted = client.Decrypt(encrypted);
// decrypted should equal plaintext
```
## Notes
- Encrypt and Decrypt only work after a 32-byte key has been provided via SetKey; otherwise Encrypt returns plaintext and Decrypt returns content unchanged.
- If the encrypted content is tampered with, the key is wrong, or the payload is malformed, Decrypt returns the special placeholder: "[encrypted message — decryption failed, try re-logging to fetch the latest key]".
- The key is held in memory and is not rotated automatically; ensure proper key management and re-fetch after key rotation on the server.
@@ -0,0 +1,32 @@
# ClipboardFiles
> **File:** `src/EchoHub.Client/Services/ClipboardFiles.cs`
> **Kind:** class
```csharp
public static class ClipboardFiles
```
ClipboardFiles reads file paths from the clipboard when the clipboard contains a file-list (such as after copying files in Explorer/Finder). Use TryGetFiles to retrieve those paths so you can attach copied files directly without pasting textual paths; this works on Windows and Linux, while macOS and other platforms do not expose a file-list clipboard.
## Remarks
ClipboardFiles encapsulates platform differences behind a single API. It isolates Windows-specific CF_HDROP handling and Linux's text/uri-list retrieval, performing path existence checks and filtering out non-file entries to return a clean list of existing paths. It returns true only when at least one file is found; otherwise false, letting callers gracefully fall back to other input methods.
## Example
```csharp
if (ClipboardFiles.TryGetFiles(out var files))
{
Console.WriteLine($"Clipboard contains {files.Count} file(s): {string.Join(", ", files)}");
}
else
{
Console.WriteLine("Clipboard does not contain a file-list or contains only non-existent paths.");
}
```
## Notes
- Returns only existing files; non-existent or inaccessible paths are ignored.
- Windows implementation relies on CF_HDROP with a brief retry loop to tolerate clipboard contention.
- Linux implementation uses wl-paste or xclip (one must be available for success).
- macOS and other platforms do not provide file-list clipboard support.
@@ -0,0 +1,29 @@
# ClipboardImage
> **File:** `src/EchoHub.Client/Services/ClipboardImage.cs`
> **Kind:** class
```csharp
public static class ClipboardImage
```
Reads raw image data from the OS clipboard and returns PNG-encoded bytes suitable for saving, embedding, or transmitting. Use this when you need a single, consistent PNG representation of whatever image the user has copied (browser-copied PNGs, screenshots, editor bitmaps) so callers don't need per-OS or per-format handling.
## Remarks
This class normalizes multiple clipboard image formats into PNG. It prefers native clipboard PNG formats when available (preserving transparency) and falls back to platform clipboard bitmaps (CF_DIB on Windows) by wrapping the DIB bytes in a minimal BMP file header and decoding/re-encoding them as PNG. TryGetPng routes to OS-specific helpers and catches/logs errors, returning false on failure rather than throwing.
## Example
```csharp
// Save whatever image is on the clipboard to a file named clipboard.png
if (ClipboardImage.TryGetPng(out var png))
{
System.IO.File.WriteAllBytes("clipboard.png", png);
}
```
## Notes
- DibToPng returns null for malformed or undecodable DIB input; TryGetPng propagates that as a failure (false).
- The implementation prefers registered PNG clipboard formats to preserve alpha; CF_DIB bitmaps are re-encoded and may lose or change metadata.
- Re-encoding a bitmap to PNG allocates memory and does CPU work; callers should avoid doing this in a tight loop.
- TryGetPng checks the platform (Windows/Linux/macOS) and will return false on unsupported platforms; failures are logged rather than thrown.
@@ -0,0 +1,63 @@
# ConnectionManager.cs
> **Source:** `src/EchoHub.Client/Services/ConnectionManager.cs`
## Contents
- [ConnectionManager](#connectionmanager)
- [ConnectResult](#connectresult)
---
## ConnectionManager
> **File:** `src/EchoHub.Client/Services/ConnectionManager.cs`
> **Kind:** class
```csharp
internal sealed class ConnectionManager : IAsyncDisposable
```
Manages the full lifecycle of a live chat connection: authenticating with the server, establishing end-to-end encryption keys, creating and wiring the SignalR (EchoHub) connection, tracking joined channels, and exposing a thin event surface that the UI (AppOrchestrator) can subscribe to. Use this when you want a single, high-level component to own connection state and SignalR event forwarding instead of manipulating ApiClient and EchoHubConnection directly.
## Remarks
This class centralizes the responsibilities that would otherwise be scattered across UI code: authentication and token rotation, attempting to fetch and apply the E2E encryption key, instantiating and wiring an EchoHubConnection, and keeping track of which channels have been joined. It forwards SignalR events as simple .NET events so the UI layer can react without needing to know SignalR details. ConnectionManager also implements IAsyncDisposable so callers can cleanly tear down both the EchoHubConnection and the underlying ApiClient.
## Notes
- ConnectionManager may raise forwarded events from background threads (SignalR callbacks). UI handlers should marshal to the UI thread if required by the UI framework.
- ConnectAsync reports progress via the onStatus callback and will throw on authentication failure; callers are expected to handle expired saved sessions or retry logic.
- Failure to fetch the encryption key is treated as non-fatal: the manager logs a warning and proceeds without message encryption.
- Dispose of the manager (DisposeAsync) when the app shuts down to ensure the hub connection and ApiClient are cleaned up.
---
## ConnectResult
> **File:** `src/EchoHub.Client/Services/ConnectionManager.cs`
> **Kind:** record
```csharp
internal record ConnectResult(
LoginResponse Login,
List<ChannelDto> Channels,
Dictionary<string, List<MessageDto>> Histories)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Login` | [`LoginResponse`](../../EchoHub.Core/DTOs/AuthDtos.cs.md) | — |
| `Channels` | `List<ChannelDto>` | — |
| `Histories` | `Dictionary<string, List<MessageDto>>` | — |
Represents the outcome of a successful connection, returned to AppOrchestrator for UI updates. It bundles the authentication result, the current set of channels, and the initial histories for every auto-joined channel (keyed by channel name and including the default channel). As an immutable record, it serves as a single, self-contained snapshot that the UI can bootstrap from after a connect.
## Remarks
This object centralizes the data needed to render the initial connected state, decoupling the connection logic from the UI orchestration. By passing a single ConnectResult, the AppOrchestrator can immediately populate channel lists and histories without issuing additional fetches, promoting a clean separation between connection handling and presentation concerns.
## Notes
- ConnectResult is immutable; to reflect changes (e.g., new messages or channels), construct and pass a new instance rather than mutating the existing one.
- Histories is a dictionary keyed by channel name that contains the initial per-channel histories; ensure channel names in the dictionary align with the Channels list to avoid inconsistencies.
---
@@ -0,0 +1,158 @@
# EchoHubConnection.cs
> **Source:** `src/EchoHub.Client/Services/EchoHubConnection.cs`
## Contents
- [ChannelPasswordRequiredException](#channelpasswordrequiredexception)
- [EchoHubConnection](#echohubconnection)
- [RoomLockedException](#roomlockedexception)
- [JoinOutcome](#joinoutcome)
---
## ChannelPasswordRequiredException
> **File:** `src/EchoHub.Client/Services/EchoHubConnection.cs`
> **Kind:** class
```csharp
public sealed class ChannelPasswordRequiredException : Exception
```
Thrown when joining a channel fails because a password is required or the provided password is incorrect. The UI catches this to prompt the user for credentials and retry the join, using ChannelName to provide channel context.
## Remarks
ChannelPasswordRequiredException provides a precise signal for a password-related join failure. By carrying the ChannelName, it enables the UI to present a meaningful prompt and retry flow without inspecting lower-level errors. This focused exception helps keep join logic cohesive and testable by separating password-entry concerns from generic failure handling.
## Example
```csharp
try
{
// Code that attempts to join a channel and may throw ChannelPasswordRequiredException
}
catch (ChannelPasswordRequiredException ex)
{
Console.WriteLine($"Password is required to join channel '{ex.ChannelName}'.");
// Prompt the user for a password and retry the join using the provided channel name
}
```
## Notes
- Be mindful that ChannelName may be null if constructed with null; guard accordingly before displaying it to users.
---
## EchoHubConnection
> **File:** `src/EchoHub.Client/Services/EchoHubConnection.cs`
> **Kind:** class
```csharp
public sealed class EchoHubConnection : IAsyncDisposable
```
A SignalR-backed client wrapper that manages a HubConnection to the Echo chat hub, integrates client-side encryption/room-key lookup, and exposes simple event callbacks for incoming messages, presence and channel events. Reach for EchoHubConnection when you need a higher-level, event-driven connection to the server that automatically handles authentication token provisioning and reconnect behavior while decrypting incoming payloads for the UI.
## Remarks
EchoHubConnection encapsulates the SignalR HubConnection lifecycle and maps server callbacks onto plain .NET events (e.g. OnMessageReceived, OnUserJoined, OnChannelUpdated). It supplies the HubConnectionBuilder with an AccessTokenProvider using the provided ApiClient so calls are authenticated, and it wires automatic-reconnect handlers that surface connection state changes via OnConnectionStateChanged and OnReconnected. Incoming MessageDto instances are passed through the client-side encryption pipeline (ClientEncryptionService and RoomKeyStore) so the UI sees decrypted content or a locked placeholder when a room key is not available.
## Example
```csharp
// Subscribe to events and inspect connection state
var echo = new EchoHubConnection(serverUrl, apiClient, encryptionService, roomKeyStore);
echo.OnMessageReceived += message =>
{
// MessageDto is provided by the library; content may be the LockedMessagePlaceholder
Console.WriteLine($"Message received in {message.ChannelName}: {message.Content}");
};
if (echo.IsConnected)
{
Console.WriteLine("Currently connected to the chat hub.");
}
// Remember to dispose when finished
await echo.DisposeAsync();
```
## Notes
- Events are raised directly from SignalR callbacks; handlers may not run on a UI thread — marshal to the UI thread if required.
- Encrypted messages for channels without a stored key are replaced with LockedMessagePlaceholder; supply the channel passphrase (through the app's key store flow) to see decrypted content.
- Call DisposeAsync to release the underlying HubConnection and related resources to avoid background network activity.
---
## RoomLockedException
> **File:** `src/EchoHub.Client/Services/EchoHubConnection.cs`
> **Kind:** class
```csharp
public sealed class RoomLockedException : Exception
```
Thrown to signal a security-sensitive condition when attempting to send a message into an end-to-end encrypted channel whose room key isn't cached. The operation is blocked to prevent sending plaintext; catching this exception lets the UI prompt for the channel's passphrase and unlock the room before retrying.
## Remarks
RoomLockedException acts as a clear boundary between encryption policy and transport logic. By exposing the ChannelName, callers can present a channel-scoped unlock prompt without parsing the error text, and the sealed Exception type communicates a concrete, expected failure mode that downstream code can handle distinctly from generic errors.
## Example
```csharp
try
{
// Simulated scenario: an attempt to send into a locked E2E channel
throw new RoomLockedException("Lobby");
}
catch (RoomLockedException ex)
{
// Use the information to drive the unlock UX
Console.WriteLine(ex.Message);
Console.WriteLine($"Unlock channel: {ex.ChannelName} by entering its passphrase.");
}
```
## Notes
- Do not swallow this as a generic error; catch RoomLockedException to trigger the unlock UX and use ex.ChannelName to identify the affected channel. The displayed message is user-facing and not localized.
---
## JoinOutcome
> **File:** `src/EchoHub.Client/Services/EchoHubConnection.cs`
> **Kind:** record
```csharp
public sealed record JoinOutcome(List<MessageDto> History, string? EncryptionSalt, string? WrappedRoomKey)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `History` | `List<MessageDto>` | — |
| `EncryptionSalt` | `string?` | — |
| `WrappedRoomKey` | `string?` | — |
JoinOutcome is a sealed record that represents the result of joining a channel: it includes the decrypted history (History) and, for end-to-end encrypted channels, the key envelope necessary to unlock the room's content key (WrappedRoomKey). EncryptionSalt is the salt used to derive the encryption key when applicable. This type is typically produced by the join logic and consumed by the UI to render messages and initialize decryption if needed.
## Remarks
This abstraction centralizes the outcome of a join into a single, immutable value that downstream components can rely on. The History is always present (even if empty), while EncryptionSalt and WrappedRoomKey are nullable to reflect that some rooms are not end-to-end encrypted or that keys may not be provisioned yet. By grouping history and encryption metadata together, the join logic can separate concerns: rendering chat versus handling cryptographic setup.
## Example
```csharp
using System.Collections.Generic;
List<MessageDto> history = new List<MessageDto>();
var joinResult = new JoinOutcome(history, null, null);
```
## Notes
- EncryptionSalt and WrappedRoomKey can be null; callers should verify non-null before attempting decryption-related steps.
---
@@ -0,0 +1,96 @@
# NativeFolderPicker.cs
> **Source:** `src/EchoHub.Client/Services/NativeFolderPicker.cs`
## Contents
- [NativeFolderPicker](#nativefolderpicker)
- [FolderPickResult](#folderpickresult)
- [PickerOutcome](#pickeroutcome)
---
## NativeFolderPicker
> **File:** `src/EchoHub.Client/Services/NativeFolderPicker.cs`
> **Kind:** class
```csharp
public static class NativeFolderPicker
```
Opens the OS-native folder picker by shelling out to the host OS, keeping the TUI free of GUI toolkit dependencies. It supports Windows, macOS, and Linux by delegating to platform-specific helpers and returns a FolderPickResult that communicates whether a folder was chosen, the dialog was cancelled, or the native picker is unavailable so the caller can fall back to a configured path.
## Remarks
NativeFolderPicker centralizes cross-platform behavior for obtaining a folder path without pulling in a GUI toolkit. It hides OS differences behind a single entry point, PickFolderAsync, and exposes a uniform result type (FolderPickResult with a PickerOutcome) that callers can inspect to either proceed with the chosen path or fall back to defaults. Failures are caught and logged, ensuring graceful degradation rather than exceptions propagating to the UI.
## Notes
- Linux will not attempt a graphical picker if no graphical session is detected (DISPLAY or WAYLAND_DISPLAY are missing); in that case, the method returns Unavailable.
- On Windows, the initial directory is sanitized (apostrophes are doubled) to safely embed the path in the PowerShell script, and PowerShell is invoked via an encoded command to avoid quoting issues.
- If the platform-specific helper cannot be started, the code falls back to returning Unavailable instead of throwing, allowing callers to implement their own fallback strategy.
---
## FolderPickResult
> **File:** `src/EchoHub.Client/Services/NativeFolderPicker.cs`
> **Kind:** record
```csharp
public sealed record FolderPickResult(PickerOutcome Outcome, string? Path)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Outcome` | `PickerOutcome` | — |
| `Path` | `string?` | — |
FolderPickResult is an immutable data carrier that represents the outcome of a native folder-picking operation and, when successful, the path of the selected folder.
## Remarks
Because FolderPickResult is a record, it benefits from value-based equality and straightforward pattern matching when consumed by calling code. The Path member is nullable to reflect that a folder may not be selected; always check the Outcome before using Path. This abstraction decouples application logic from platform-specific picker implementations, promoting testability and cross-platform compatibility.
## Example
```csharp
var result = new FolderPickResult(PickerOutcome.Success, @"C:\Projects");
if (result.Outcome == PickerOutcome.Success && result.Path is not null)
{
Console.WriteLine(result.Path);
}
```
## Notes
- Path may be null when Outcome indicates cancellation or failure; always verify Outcome before accessing Path.
---
## PickerOutcome
> **File:** `src/EchoHub.Client/Services/NativeFolderPicker.cs`
> **Kind:** enum
```csharp
public enum PickerOutcome
{
Chosen,
Cancelled,
Unavailable,
}
```
PickerOutcome encodes the result of attempting to display a native folder picker. It defines three mutually exclusive states: Chosen (the user picked a folder and FolderPickResult.Path is set), Cancelled (the native dialog ran but no selection was made), and Unavailable (no native picker is available on the current machine).
Use this enum to drive post-pick logic without scattering platform checks or error handling across call sites.
## Remarks
This enum serves as a lightweight sum type for the outcome of a folder-picking operation. It centralizes decision points and pairs with FolderPickResult to obtain the actual path when Chosen is returned. Consumers can implement a fallback flow for Unavailable and provide a smooth user experience when Cancelled.
## Notes
- Unavailable is not an error; it indicates the absence of a native picker and warrants a fallback strategy (e.g., a non-native picker or manual path entry).
---
@@ -0,0 +1,19 @@
# NotificationSoundService
> **File:** `src/EchoHub.Client/Services/NotificationSoundService.cs`
> **Kind:** class
```csharp
public class NotificationSoundService
```
NotificationSoundService centralizes the playback of the notification sound. It resolves the sound file from configuration (if specified and found) or falls back to a bundled default, then plays the sound at a configurable volume when requested. The service exposes SetEnabled and SetVolume for simple runtime tuning, and PlayAsync for normal operation or PlayTestAsync for QA scenarios where playback should occur regardless of the Enabled flag. Internally it uses a semaphore to serialize concurrent playback, and a 10-second timeout to prevent a stuck caller if the sound does not finish.
## Remarks
The class isolates all concerns around audio playback: path resolution, volume handling, concurrency, and fault tolerance. By hiding these details behind a single service, higher-level notification logic can simply request a sound without worrying about file presence, logging, or synchronization. The design anticipates environments where a sound file might be missing or playback might stall, and it ensures resources are released and the system remains responsive.
## Notes
- Silent fallback if a sound file cannot be found; production environments should ensure the asset exists if audible alerts are required.
- The PlaybackFinished event and the 10-second timeout guard the system against hangs; the lock may be released before the sound finishes, which means subsequent playback requests can start while a prior one is still playing.
- PlayAsync respects the Enabled flag, while PlayTestAsync allows testing the sound regardless of Enabled.
@@ -0,0 +1,46 @@
# OutgoingAttachment
> **File:** `src/EchoHub.Client/Services/OutgoingAttachment.cs`
> **Kind:** record
```csharp
public sealed record OutgoingAttachment(
Stream Stream,
string FileName,
string? DeclaredKind = null,
string? EncryptedPreview = null)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Stream` | `Stream` | — |
| `FileName` | `string` | — |
| `DeclaredKind` | `string?` | `null` |
| `EncryptedPreview` | `string?` | `null` |
OutgoingAttachment is a transport object that represents a single file to upload as part of a message. It bundles the data Stream and FileName, and optionally carries DeclaredKind and EncryptedPreview for encrypted channels, while non-encrypted channels typically set only Stream and FileName.
## Remarks
OutgoingAttachment serves as a compact, immutable data carrier that travels through the sending pipeline. As a record, it uses value-based equality which helps comparisons and deduplication when attachments are tracked across requests. It also clarifies ownership: the record does not manage the lifetime of the underlying Stream; callers are responsible for opening and disposing streams as appropriate.
## Example
```csharp
using System.IO;
// Normal channel usage: only Stream and FileName are provided
var data = new byte[] { 0x01, 0x02, 0x03 };
var stream = new MemoryStream(data);
var attachment = new OutgoingAttachment(stream, "data.bin");
// End-to-end encrypted channel usage: DeclaredKind and EncryptedPreview are set
var ciphertext = new MemoryStream(new byte[] { 0xAA, 0xBB, 0xCC });
var asciiPreview = @"ASCII_ART_PREVIEW";
var encryptedAttachment = new OutgoingAttachment(ciphertext, "image.png", "image", asciiPreview);
```
## Notes
- The lifetime of the underlying Stream is not managed by OutgoingAttachment; the caller must ensure the stream is disposed when appropriate.
- DeclaredKind and EncryptedPreview are intended for encrypted channels; in normal channels these values are typically null.
@@ -0,0 +1,24 @@
# PathSetup
> **File:** `src/EchoHub.Client/Services/PathSetup.cs`
> **Kind:** class
```csharp
public static class PathSetup
```
PathSetup is a cross-platform helper that ensures the application's directory is present on the system PATH, enabling commands like echohub to be run from any terminal session without specifying the full path. EnsureOnPath checks for the directory and, if missing, updates PATH in a platform-appropriate way: Windows updates the user PATH; Unix-like systems append an export line to common shell profile files.
## Remarks
By centralizing PATH manipulation, this abstraction reduces code duplication and the risk of divergent PATH states across platforms. It uses a lightweight, best-effort approach and logs outcomes to aid diagnostics when PATH updates fail or are skipped. The addition is clearly marked by a PathMarker to avoid duplicating lines in shell profiles.
## Example
```csharp
PathSetup.EnsureOnPath();
```
## Notes
- The method swallows exceptions and logs at debug level, so callers should not rely on exceptions to signal failure.
- Unix updates affect the user's shell environment; new terminal sessions are typically required to observe changes.
- Windows updates are done at the per-user level; system-wide PATH is not modified.
@@ -0,0 +1,30 @@
# RoomKeyProtector
> **File:** `src/EchoHub.Client/Services/RoomKeyProtector.cs`
> **Kind:** class
```csharp
public sealed class RoomKeyProtector
```
Encrypts cached room content keys at rest so the client config never holds them as plain base64. Windows uses DPAPI (current-user scope, format prefix "dp1:"). On other platforms the keys are AES-GCM encrypted with a per-user master key file stored next to the config with 0600 permissions (prefix "k1:") — without an OS keychain that is file-permission-level protection, not zero-knowledge: anyone who can read both the config and the key file can recover the room keys. Values with no recognized prefix are legacy plain-base64 keys from older clients; they load once and are re-encrypted. The room passphrase itself is never stored in any form.
The RoomKeyProtector class provides a single API surface to protect and unprotect per-user room keys across platforms. The Protect method returns a string suitable for storage in the config, automatically selecting the appropriate protection mechanism for the current OS (DPAPI on Windows, file-based AES-GCM on others). TryUnprotect decodes a stored value back into a room key, reporting whether the value was a legacy (unencrypted) entry and whether the decryption succeeded. The implementation intentionally hides platform differences behind a consistent interface, so callers can persist and reload keys without worrying about the underlying cryptosystem.
The constructor accepts a directory that holds the master key file and an optional flag to override the OS-provided protection path (useful for tests). The key file path is derived from the directory by appending the fixed file name roomkeys.key. Key loading is guarded by a small lock and the master key is cached after the first read. The Protect path prefixes the output to indicate how the data is protected ("dp1:" or "k1:").
The class ensures the room passphrase itself is never persisted, and it gracefully tolerates missing or unreadable key material by returning false from TryUnprotect (leaving the caller to prompt the user for action).
````csharp
// Typical usage
var protector = new RoomKeyProtector("/config");
byte[] roomKey = new byte[32]; // obtain from a secure source
string stored = protector.Protect(roomKey);
if (protector.TryUnprotect(stored, out var recovered, out bool wasLegacy))
{
// recovered contains the room key if the value was decryptable
// wasLegacy is true only if the input was a legacy base64 key without a prefix
}
````
@@ -0,0 +1,43 @@
# RoomKeyStore
> **File:** `src/EchoHub.Client/Services/RoomKeyStore.cs`
> **Kind:** class
```csharp
public sealed class RoomKeyStore
```
Holds and manages end-to-end encrypted room keys for a single client instance: it keeps a decrypted, in-memory cache for the active session and a per-server persisted, encrypted copy so users do not have to re-enter passphrases each launch. Use RoomKeyStore when you need a thread-safe local store that provides room keys to the runtime and ensures keys are encrypted at rest via RoomKeyProtector.
## Remarks
RoomKeyStore links transient runtime state with the client's persisted configuration. It binds to a server (LoadForServer), loads that server's saved ChannelKeys (unprotecting them with RoomKeyProtector), and exposes methods to read, add, replace, or remove keys while persisting changes back to the SavedServer entry. It also records which channels are known to be encrypted so callers can avoid emitting plaintext into rooms without a cached key. The class performs a one-way upgrade of legacy unprotected entries to the protected format when possible and logs unreadable entries rather than failing.
## Example
```csharp
var store = new RoomKeyStore();
store.LoadForServer("https://chat.example.com");
// Generate and store a new room key for a channel
byte[] newKey = RoomCrypto.GenerateRoomKey();
store.StoreKey("#team-room", newKey);
// Retrieve a key for sending encrypted messages
if (store.TryGetKey("#team-room", out var key))
{
// Use `key` with RoomCrypto API to encrypt message content
}
// Accept an encrypted envelope and store the unwrapped key only if the KEK opens it
string wrapped = "..."; // envelope string received
byte[] kek = /* key-encryption-key */ new byte[RoomCrypto.KeySizeBytes];
if (store.TryStoreFromEnvelope("#other-room", wrapped, kek))
{
// successfully unwrapped and cached
}
```
## Notes
- Call LoadForServer(serverUrl) before persisting or retrieving server-scoped keys; the store clears and reinitializes its cache when bound to a server.
- Legacy (plain/base64) saved entries are upgraded to the protector-backed format when possible; entries that cannot be unprotected are ignored and logged.
- The class uses an internal lock for basic thread-safety of the in-memory cache; avoid holding returned keys while performing long synchronous work that might race with store mutations.
@@ -0,0 +1,86 @@
# UpdateBackupService.cs
> **Source:** `src/EchoHub.Client/Services/UpdateBackupService.cs`
## Contents
- [BackupJsonContext](#backupjsoncontext)
- [UpdateBackupService](#updatebackupservice)
- [BackupInfo](#backupinfo)
---
## BackupJsonContext
> **File:** `src/EchoHub.Client/Services/UpdateBackupService.cs`
> **Kind:** class
```csharp
[System.Text.Json.Serialization.JsonSerializable(typeof(BackupInfo))]
internal partial class BackupJsonContext : System.Text.Json.Serialization.JsonSerializerContext
```
BackupJsonContext is an internal partial class that provides the source-generated JSON serialization metadata for the BackupInfo type. It plugs into System.Text.Jsons source generator, enabling reflection-free serialization of BackupInfo when you configure a JsonSerializerOptions with this context.
## Remarks
This symbol acts as the concrete carrier of serialization metadata for BackupInfo within the JSON pipeline of EchoHubs client. By centralizing the generated type information in a single context, it keeps serialization concerns isolated from business logic and allows the type to evolve without scattering attributes across multiple call sites. The pattern here—one generated context per data contract—supports predictable performance improvements while preserving a clean, minimal public surface.
## Notes
- The symbol is internal; it is intended for use within the containing assembly, not by external callers.
- The class is generated and partial; do not edit it by hand, as changes will be overwritten by the source generator.
- If you modify the BackupInfo shape, you must re-run code generation to keep the context in sync with the data contract.
---
## UpdateBackupService
> **File:** `src/EchoHub.Client/Services/UpdateBackupService.cs`
> **Kind:** class
```csharp
public static class UpdateBackupService
```
Manages pre-update backups for the auto-updater and provides rollback support by snapshotting the running application prior to an update. Backups are stored under ~/.echohub/update-backup/ as backup.zip with a companion backup-info.json that records the version, application directory, and UTC timestamp. Use CreateBackup before applying an update; verify presence with BackupExists and inspect metadata with GetBackupInfo to drive a rollback if needed. The IsPostUpdate flag signals that a backup from a recent update exists, allowing startup logic to react accordingly.
---
## BackupInfo
> **File:** `src/EchoHub.Client/Services/UpdateBackupService.cs`
> **Kind:** record
```csharp
public record BackupInfo(
string Version,
string AppDirectory,
DateTimeOffset CreatedAt)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Version` | `string` | — |
| `AppDirectory` | `string` | — |
| `CreatedAt` | `DateTimeOffset` | — |
BackupInfo is a lightweight, value-like record that encapsulates metadata about a created backup. It carries the backup Version, the AppDirectory that was backed up, and the CreatedAt timestamp, enabling complete backup metadata to be passed around as a single unit.
## Remarks
BackupInfo, being a record with positional parameters, is immutable and benefits from value-based equality. This makes it ideal as a canonical data carrier when the UpdateBackupService reports or persists backup information, or when UI/logging layers need to compare or display backup entries.
## Example
```csharp
var backup = new BackupInfo(
Version: "1.2.3",
AppDirectory: "/opt/MyApp",
CreatedAt: DateTimeOffset.UtcNow
);
```
## Notes
- Records provide structural equality; two instances with the same Version, AppDirectory, and CreatedAt compare as equal.
- CreatedAt uses DateTimeOffset to preserve offset information; prefer UTC (DateTimeOffset.UtcNow) when constructing backups to avoid timezone ambiguities.
---
@@ -0,0 +1,36 @@
# UpdateChecker
> **File:** `src/EchoHub.Client/Services/UpdateChecker.cs`
> **Kind:** class
```csharp
public sealed class UpdateChecker : IDisposable
```
Checks for application updates in the background, presents a TUI confirmation dialog when a new version is available, and defers the actual download/extract/restart work until after the terminal UI has been shut down. Use this class when the host application runs a Terminal.Gui main loop and needs a safe way to offer in-place updates without deadlocking the console or performing heavy I/O while the TUI still owns the terminal.
## Remarks
This class encapsulates polling and manual update checks via an internal Updater instance and marshals user interaction back onto the provided IApplication main loop using _app.Invoke. When the user confirms an update, UpdateChecker does not perform the network/download work immediately; instead it sets PendingUpdate to an awaitable callback (ApplyUpdateAsync), stores the selected version, and requests the TUI to stop. The host is expected to call PendingUpdate after the main loop exits and the console has been restored so the update process can safely run headless (the Updater's update flow may restart the process and call Environment.Exit).
## Example
```csharp
// During application startup
var updateChecker = new UpdateChecker(app);
updateChecker.Start(); // starts periodic checks in RELEASE builds
// ... run Terminal.Gui main loop ...
// After the main loop exits and the console is restored, run any pending update
if (updateChecker.PendingUpdate != null)
{
await updateChecker.PendingUpdate();
}
```
## Notes
- Start only activates the background poller in RELEASE builds (the Start method is no-op in non-RELEASE builds).
- PendingUpdate is deliberately set to a Task-returning delegate and intended to be invoked by the host after the TUI has fully stopped; running it while the TUI still owns the console can deadlock the restart flow.
- ApplyUpdateAsync attempts to create a pre-update backup with UpdateBackupService.CreateBackup; backup creation failures are logged and the update continues.
- ApplyUpdateAsync sets Console.OutputEncoding = UTF8 but swallows exceptions (useful when stdout is redirected or non-interactive).
- CurrentVersion reads the assembly version and falls back to "0.0.0" if unavailable.
@@ -0,0 +1,19 @@
# UserSession
> **File:** `src/EchoHub.Client/Services/UserSession.cs`
> **Kind:** class
```csharp
internal sealed class UserSession
```
Stores the current user's session state on the client, including username, online status, and an optional status message. Use this type as a lightweight, centralized container when you need to read or mutate the ephemeral session data for the active user, and call Reset to return all fields to their defaults (empty username, Online status, and no status message).
## Remarks
Internally sealed and non-public, this class keeps the session representation stable within the client service layer and prevents inheritance. It relies on the UserStatus enum from the core models to express the user's current state consistently across the application.
## Notes
- Not thread-safe by default; coordinate concurrent access if used from multiple threads.
- Reset mutates state in place; if you require preserving data, capture it before calling Reset.
- StatusMessage is nullable; null indicates that no message is provided.
@@ -0,0 +1,52 @@
# Theme.cs
> **Source:** `src/EchoHub.Client/Themes/Theme.cs`
## Contents
- [Theme](#theme)
- [ThemeColors](#themecolors)
---
## Theme
> **File:** `src/EchoHub.Client/Themes/Theme.cs`
> **Kind:** class
```csharp
public class Theme
```
Theme is the central container for theming the EchoHub client UI. It holds a Name and four color palettes (Base, Menu, Dialog, Status) used across the main window and its chrome; plus an optional Border palette that can override only the frame borders while the rest remains tied to Base. If Border is null, the border colors fall back to the Base palette, letting themes tone borders down independently from text to achieve effects like glassy translucency. The palettes default to new ThemeColors instances, so a Theme is immediately usable and developers only configure what they need. Border supports hex literals like "#6E6E6E" and named colors, enabling quick tweaks without changing the rest of the palette.
## Remarks
Theme isolates brand identity and UI chrome from layout logic, enabling themes to be swapped at runtime or per user preference. The per-area color groups—Base, Menu, Dialog, and Status—provide visual consistency while allowing targeted overrides; Border offers a focused knob for edge treatment without touching text colors. This composition reduces duplication: a single Theme can render across the chrome, with optional Border overrides to achieve distinctive looks without rewriting color logic.
## Notes
- Name is marked as required; always provide a non-empty value during initialization.
- Border is nullable. If you don't set it, the UI uses Base colors for borders; set Border when you want to tint borders independently.
- Hex codes and named colors: ensure strings you assign are valid color tokens understood by the theming system to avoid fallback or misrendering.
---
## ThemeColors
> **File:** `src/EchoHub.Client/Themes/Theme.cs`
> **Kind:** class
```csharp
public class ThemeColors
```
ThemeColors is a small data container that holds the color choices used by the UI theme. It exposes four properties—Foreground, Background, FocusForeground, and FocusBackground—each with a sensible default (White on Black for normal state, and White on Blue for focused state). This class centralizes theming values so UI components can render consistently and themes can be swapped by supplying a ThemeColors instance rather than scattering color literals throughout rendering code.
## Remarks
- It acts as a cohesive value object for theming, separating concerns between color data and rendering logic.
- It enables swapping themes by replacing one ThemeColors instance rather than modifying rendering code.
- It is mutable, allowing runtime theme adjustments; if a ThemeColors instance is shared across threads, consider synchronization to avoid race conditions.
## Notes
- If you mutate and share ThemeColors across threads, you may encounter race conditions; prefer per-thread copies or proper synchronization when updating values.
---
@@ -0,0 +1,601 @@
# ThemeManager.cs
> **Source:** `src/EchoHub.Client/Themes/ThemeManager.cs`
## Contents
- [ThemeManager](#thememanager)
- [ApplyTheme](#applytheme)
- [BuildColorScheme](#buildcolorscheme)
- [GetAvailableThemes](#getavailablethemes)
- [GetTheme](#gettheme)
- [ParseColor](#parsecolor)
- [SaveTheme](#savetheme)
- [ClassicTheme](#classictheme)
- [DefaultTheme](#defaulttheme)
- [DraculaTheme](#draculatheme)
- [HackerTheme](#hackertheme)
- [HighContrastTheme](#highcontrasttheme)
- [JsonOptions](#jsonoptions)
- [LightTheme](#lighttheme)
- [MonokaiTheme](#monokaitheme)
- [OceanTheme](#oceantheme)
- [SolarizedTheme](#solarizedtheme)
- [ThemeDir](#themedir)
- [TransparentLightTheme](#transparentlighttheme)
- [TransparentTheme](#transparenttheme)
- [BuiltInThemes](#builtinthemes)
- [GruvboxTheme](#gruvboxtheme)
- [NordTheme](#nordtheme)
- [RosePineTheme](#rosepinetheme)
---
## ThemeManager
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** class
```csharp
public static class ThemeManager
```
ThemeManager_overview provides a centralized, static API for discovering, loading, applying, and persisting themes used by the EchoHub client UI. Call GetAvailableThemes to enumerate built-in and user-defined themes, GetTheme to fetch a theme by name, and ApplyTheme to switch the UI to a chosen theme.
## Remarks
Conceptually, ThemeManager acts as the bridge between Theme data (the Theme class) and the runtime UI. It maintains a curated list of built-in themes and exposes logic to load additional themes from a user directory, surfacing them for selection without requiring changes to the runtime code. In addition, BuildColorScheme ensures color assignments for text areas align with the active theme, pinning Editable/ReadOnly roles so that transparent themes render correctly and inputs stay legible. This centralizes theming concerns and keeps theme-related behavior in one place, simplifying maintenance and experimentation with new themes.
## Example
```csharp
var available = ThemeManager.GetAvailableThemes();
var theme = ThemeManager.GetTheme("Default");
ThemeManager.ApplyTheme(theme);
```
## Notes
- SaveTheme is best-effort and silently swallows failures; verify persistence if you rely on saved themes.
- GetAvailableThemes falls back to built-in themes when the theme directory cannot be read.
- ParseColor expects valid color identifiers defined by the theming system; supply colors that exist in the library or your Theme colors.
---
### ApplyTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
public static void ApplyTheme(Theme theme)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `theme` | [`Theme`](Theme.cs.md) | — |
**Returns:** `void`
Applies a Theme by registering color schemes for the core UI areas with SchemeManager. This single call maps the Theme's Base, Menu, Dialog, and optional Border sections to named schemes so the rest of the UI can render consistently according to the active theme.
## Remarks
This method acts as a bridge between the Theme model and SchemeManager's scheme registry. It delegates color construction to BuildColorScheme for each region, ensuring Base, Menu, and Dialog colors stay in sync. The Border scheme uses theme.Border when provided, otherwise it falls back to the Base palette to preserve a coherent frame. By applying all four schemes in one place, ApplyTheme reduces the risk of components diverging toward inconsistent styling.
## Notes
- Repeatedly calling ApplyTheme overwrites the previously registered schemes, so batch theme updates if you want to avoid intermediate flashes.
- The Border palette falls back to Base when Border is not provided; ensure the Base colors reflect the desired frame in that case.
## Dependencies
- SchemeManager
---
### BuildColorScheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
private static Scheme BuildColorScheme(ThemeColors colors)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `colors` | [`ThemeColors`](Theme.cs.md) | — |
**Returns:** `Scheme`
BuildColorScheme converts ThemeColors into a Terminal.Gui Scheme by deriving two Attributes—Normal from Foreground and Background and Focus from FocusForeground and FocusBackground—then applying them to the Scheme's state properties (Normal, Focus, HotNormal, HotFocus, Disabled). It also pins Editable and ReadOnly to Normal to ensure input controls render against the theme background, avoiding opaque boxes in transparent themes.
## Remarks
This method centralizes the theme-to-scheme translation, decoupling ThemeColors from the Scheme used by the UI. By deriving Normal and Focus once and reusing them for all relevant roles, and by tying Editable/ReadOnly to Normal, it guarantees consistent visual behavior for standard controls and editable regions across themes. The method being private static signals that it's an internal detail of the theming pipeline used by ThemeManager to assemble the active color scheme.
## Notes
- If ThemeColors contain invalid color strings, ParseColor may throw; ensure colors are validated before calling BuildColorScheme.
- The returned Scheme is a new object each time; repeated calls may impact allocations.
- Editable and ReadOnly are deliberately mapped to Normal; if you need distinct input backgrounds, adjust the mapping accordingly.
---
### GetAvailableThemes
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
public static List<Theme> GetAvailableThemes()
```
**Returns:** `List<Theme>`
The GetAvailableThemes method returns a list of Theme objects by starting with the built-in themes and augmenting that set with user-defined themes discovered in the ThemeDir directory. It iterates over all *.json files, deserializes each one into a Theme using JsonSerializer with the configured JsonOptions, and, if the resulting theme has a non-empty Name and does not duplicate an existing theme (case-insensitive comparison on Name), appends it to the collection. If ThemeDir does not exist or any IO or JSON parsing error occurs, the method gracefully falls back to returning only the built-in themes.
## Remarks
This function encapsulates the theme-loading strategy: built-in themes establish the default baseline, while external JSON themes extend the collection without mutating the originals. It operates defensively, skipping malformed files and continuing execution in the face of read errors, which yields a predictable return value even under partial failure. De-duplication is driven by Theme.Name using a case-insensitive comparison to prevent accidental duplicates when names differ only by case.
## Notes
- It swallows IO and JSON parsing exceptions, so failures to read or parse individual files do not propagate to the caller.
- Built-in themes take precedence: a user-defined theme with a Name that matches an existing built-in theme is ignored, ensuring stable baseline behavior.
---
### GetTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
public static Theme GetTheme(string name)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `name` | `string` | — |
**Returns:** [`Theme`](Theme.cs.md)
Resolves a Theme by name by searching the collection returned by GetAvailableThemes and returning the first match found when the theme name equals the provided name, ignoring case. It is the right choice when you need to map a user-provided theme name (from UI, config, or input) to a Theme object, with a fallback to DefaultTheme if no match exists.
## Remarks
By centralizing theme resolution in this single method, callers can map a string (for example, user input) to a Theme object without duplicating comparison logic or null checks. The use of ordinal string comparison ensures consistent, culture-invariant matching across locales. The method relies on GetAvailableThemes providing a valid collection and on DefaultTheme representing a concrete theme.
## Notes
- If GetAvailableThemes returns null, the call to Find will throw a NullReferenceException.
- The search is linear in the size of the themes collection; for large catalogs consider caching or indexing to improve lookup performance.
- Name comparison uses OrdinalIgnoreCase; if you need culture-aware matching, replace with a culture-aware comparison or normalize names elsewhere.
---
### ParseColor
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
private static Color ParseColor(string colorName)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `colorName` | `string` | — |
**Returns:** `Color`
Parses a color name into a Color using Color.TryParse. If parsing succeeds, it returns the resulting Color (or White if the parsed color is null). If parsing fails, it returns Color.White. Use this helper when theme code needs to translate a color name string into a Color value, ensuring a valid color is always returned instead of propagating nulls.
## Remarks
Centralizes color-name parsing, reducing duplication and guarding ThemeManager's rendering paths against invalid color inputs. The fallback to White makes the UI predictable but at the risk of hiding misconfigurations; consider logging when a fallback occurs to aid debugging.
## Notes
- Invalid or unknown color names yield Color.White without throwing.
- No exception is thrown; a deterministic Color is always returned.
---
### SaveTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** method
```csharp
public static void SaveTheme(Theme theme)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `theme` | [`Theme`](Theme.cs.md) | — |
**Returns:** `void`
Persists a Theme by serializing it to JSON and writing it to a file named after the theme under ThemeDir. Use SaveTheme to persist a user-selected theme so it can be reloaded on startup; it's a best-effort operation that silently swallows failures, so callers shouldn't rely on it for critical persistence.
## Remarks
This abstraction encapsulates the simple idea of theme persistence: ensure the target directory exists, determine a file path from the Theme.Name, serialize to JSON using JsonOptions, and write the content. It uses Theme.Name as the file name, so two themes with the same name will overwrite each other; an enhanced naming strategy or unique IDs could help. Failures are swallowed, so any persistence failure is invisible to the caller; consider adding logging or a higher-level retry if persistence must be durable. The method depends on JsonOptions for serialization behavior and relies on the standard IO primitives (Directory, Path, JsonSerializer, File).
## Notes
- The catch-all block hides errors; callers cannot detect save failures.
- Using Theme.Name directly as a file name may introduce invalid characters or path traversal risks if Name isn't sanitized.
- Existing theme JSON will be overwritten without backup or versioning.
---
### ClassicTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme ClassicTheme = new()
```
ClassicTheme is a private static readonly Theme that encapsulates the classic visual styling used by the UI. It defines the 'Classic' theme name and assigns color palettes for four UI zones — Base, Menu, Dialog, and Status — so the theming system can render consistent foregrounds, backgrounds, and focus states across the application.
## Remarks
Why this abstraction exists: centralizes the classic color palette in one place, avoiding repetitive literals across components. It also stabilizes the look by exposing a single instance that the ThemeManager can switch to internally to apply the classic aesthetic. In short, ClassicTheme acts as the canonical, versioned styling bundle for the traditional UI appearance.
## Notes
- Potential mutability: If Theme or ThemeColors expose public setters, the colors may be mutated after initialization. Consumers should rely on a stable palette or the code should enforce immutability.
- Accessibility considerations: The palette uses high-contrast combinations (e.g., White foreground on DarkGray/Blue). If your accessibility requirements change, adjust this Theme instance or provide alternative themes.
---
### DefaultTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme DefaultTheme = new()
```
Defines the canonical default theme used by the UI components within the ThemeManager. This private static readonly field initializes a single Theme instance named 'Default' with color settings for each UI region (Base, Menu, Dialog, Status). The nested ThemeColors specify the foreground, background, and focus colors, establishing a consistent look-and-feel across the application unless overridden by other theme configurations. Because it is static and readonly, the instance is created once at type initialization and cannot be reassigned, ensuring all consumers relying on the default palette see the same values.
## Remarks
Centralizes the default visual styling to ensure a single, shared baseline across the UI. It prevents scattering color choices across components and makes it easier to reason about the default appearance of the application. If a different baseline is needed for testing or special scenarios, a separate Theme can be created and applied through the ThemeManager, rather than modifying this field.
## Notes
- The field is private; external code cannot access or mutate DefaultTheme directly.
- Even though the reference is readonly, the nested ThemeColors objects may be mutable if their properties are settable; treat the default palette as effectively immutable at runtime unless you deliberately mutate its contents within ThemeManager.
- The color values are provided as names (e.g., 'Gray', 'White'); ensure the rendering layer recognizes these tokens to avoid unexpected visuals.
---
### DraculaTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme DraculaTheme = new()
```
DraculaTheme is a private static readonly Theme field that represents the Dracula-inspired color palette used by the theme system. It defines distinct color specifications for four UI surfaces—Base, Menu, Dialog, and Status—each with a foreground color, a background color, and explicit focus colors to ensure consistent, high-contrast visuals across the application. This field is intended for internal use by ThemeManager to apply a cohesive dark theme; external code should not rely on it directly.
## Remarks
Having a single DraculaTheme instance centralizes the Dracula look, preventing drift in color choices across components. By keeping it private and readonly, ThemeManager can switch to Dracula without duplicating palettes, while still allowing other themes to be composed similarly. The explicit focus colors help maintain clear keyboard-navigation states even on dark surfaces.
## Notes
- Private visibility prevents external code from referencing DraculaTheme directly.
- It is static readonly and assigned once; runtime mutation is not expected.
- Token names like BrightMagenta and Magenta map to concrete colors in the rendering layer; ensure the color system supports these tokens for accurate rendering.
---
### HackerTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme HackerTheme = new()
```
HackerTheme is a private, static, readonly Theme instance that encodes the color palette used by the Hacker appearance within the UI. It defines the colors for the Base, Menu, Dialog, and Status areas, providing a single source of truth that ThemeManager can apply to render a consistent dark-themed interface.
## Remarks
This field centralizes the Hacker color scheme, ensuring consistent foreground/background pairs across all UI regions and their focus states. Because HackerTheme is private to ThemeManager, external code cannot reference or mutate it directly; changes to the palette must go through ThemeManager's public API or future extensions. The nested ThemeColors per region make it easy to tweak the palette in one place when refining the visual language.
## Notes
- The static readonly modifier means HackerTheme is initialized once and its reference cannot be reassigned, but the contained ThemeColors objects may still be mutable depending on their type.
- External code should not rely on HackerTheme having a public accessor; to reuse the palette publicly, ThemeManager should expose a proper API rather than exposing internal details.
---
### HighContrastTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme HighContrastTheme = new()
```
Defines a private static readonly Theme instance named HighContrastTheme that captures a high-contrast color scheme used by the theming subsystem. It specifies color configurations for the Base, Menu, Dialog, and Status surfaces to maximize legibility and clearly indicate focus against a dark background.
## Remarks
HighContrastTheme centralizes the high-contrast styling to avoid scattering color values throughout the codebase. The ThemeManager can switch to this theme to satisfy accessibility requirements without exposing public API changes.
## Notes
- The nested ThemeColors objects may be mutable; mutating them would undermine the high-contrast guarantee. Treat HighContrastTheme as an internal constant and avoid altering its color properties at runtime.
---
### JsonOptions
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly JsonSerializerOptions JsonOptions = new()
```
JsonOptions is a privately scoped, preconfigured JsonSerializerOptions instance used by ThemeManager to serialize JSON with the projects conventions. It enables indented output and camelCase property naming, ensuring that any JSON emitted while theming is both human-readable and aligned with the API surface.
## Remarks
By using a private static readonly field, ThemeManager avoids repeated allocations and guarantees a single shared configuration for its JSON serialization within the class. Note that JsonSerializerOptions is mutable; while the field reference cannot be reassigned, changing its properties at runtime can lead to subtle, cross-call side effects. Treat this instance as effectively immutable after initialization.
## Example
```csharp
// Within ThemeManager
var data = new { Theme = "Dark", Version = 1 };
string json = JsonSerializer.Serialize(data, JsonOptions);
```
## Notes
- Mutating JsonOptions at runtime can cause inconsistent formatting across serialized outputs; prefer making changes only during initialization.
- This field is internal to ThemeManager; if different parts of the application require alternative formatting, construct and pass their own JsonSerializerOptions instead of reusing JsonOptions.
---
### LightTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme LightTheme = new()
```
LightTheme is a predefined Theme instance that encodes the light-mode color configuration used by the UI. It centralizes the color values for the base surface and for Menu, Dialog, and Status regions so the theming system can apply a consistent light appearance without constructing a new Theme object each time.
## Remarks
By consolidating the light palette in a single static object, LightTheme ensures visual consistency across components that render base surfaces, menus, dialogs, and status bars. It serves as a canonical reference for the light aesthetic within the theming subsystem, enabling ThemeManager to switch to a known, shared configuration. Because the field is private static readonly, it should be treated as a shared, effectively immutable source at runtime; mutating its nested color objects could lead to inconsistent visuals.
## Notes
- It is a static shared instance; mutating its nested ThemeColors at runtime would have global effects; treat as read-only after initialization.
---
### MonokaiTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme MonokaiTheme = new()
```
MonokaiTheme is a private static readonly field that defines the Monokai color palette used by the theme system. It holds a Theme named "Monokai" composed of four color blocks (Base, Menu, Dialog, Status), each described by ThemeColors with specific foreground, background, and focus colors. This single, shared instance provides a consistent color vocabulary for the UI, allowing ThemeManager and related rendering code to apply the Monokai look uniformly without scattering literals across the codebase. Because the field is private, its usage is internal to the class that declares it.
## Remarks
MonokaiTheme serves as a centralized, reusable color configuration for the Monokai look. By grouping color sets into Base, Menu, Dialog, and Status, it expresses distinct chrome regions while keeping a single source of truth for the palette. This abstraction makes it straightforward for ThemeManager and UI components to consistently apply the Monokai styling.
## Notes
- The Theme and ThemeColors instances are mutable; altering their properties would mutate the shared theme at runtime and affect all consumers within the process.
- External code cannot replace MonokaiTheme, but internal code could adjust its nested properties unless immutability is enforced; consider making Theme/ThemeColors immutable if a fixed theme is intended.
---
### OceanTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme OceanTheme = new()
```
OceanTheme is a predefined ocean-inspired color palette represented as a Theme instance. It groups color configurations for four UI regions—Base, Menu, Dialog, and Status—each with foreground, background, and focus colors, enabling a cohesive look across the application. The field is private static readonly, so the same Theme object is created once and reused, preventing accidental reassignment while keeping internal mutability restricted to the defining class.
## Remarks
Centralizes theming decisions and reduces duplication by providing a single, cohesive palette that UI components can rely on. OceanTheme expresses a clear design intent (an ocean-like aesthetic) and is intended to be selected by theming logic to apply a consistent appearance across Base, Menu, Dialog, and Status surfaces. The per-area ThemeColors allow distinct focus and interaction states while preserving a unified visual language.
## Notes
- Access is private to the ThemeManager class, preventing external code from directly reusing or mutating OceanTheme.
- The reference is readonly, so the field cannot be reassigned; internal mutability would require explicit code within the defining class.
- The color tokens (e.g., BrightCyan, DarkBlue, White, DarkCyan) must be valid tokens within the projects visual system for the palette to render correctly.
---
### SolarizedTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme SolarizedTheme = new()
```
SolarizedTheme is a private static readonly Theme instance that encapsulates the Solarized color palette used by the UI. It defines color roles for four UI surfaces—Base, Menu, Dialog, and Status—specifying both normal foreground/background and focused-state foreground/background colors. The field is initialized once at type-load time and is then reused wherever a Solarized look is required, providing a single source of truth for this color scheme and preventing runtime mutations.
## Remarks
This symbol acts as a centralized, immutable specification of the Solarized look. By housing the color tokens in a single Theme, ThemeManager can consistently apply the same palette across menus, dialogs, and status lines without scattering literals throughout the code. The private static readonly pattern communicates intent: SolarizedTheme is a predefined, non-changing theme available to internal consumers of ThemeManager, not something that should be modified at runtime.
## Notes
- The theme uses string color tokens (e.g., "Cyan", "BrightYellow"), which are resolved by the theming subsystem to actual display colors.
- Because the field is readonly, any changes require rebuilding the Theme instance; runtime mutation is prevented.
- The four ThemeColors sections (Base, Menu, Dialog, Status) each specify both normal and focused color states to support focus indication.
---
### ThemeDir
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly string ThemeDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".echohub", "themes")
```
ThemeDir is a private, static readonly string that resolves to the user-specific themes directory by combining the current users profile folder with .echohub/themes. It provides a single, OS-agnostic path for ThemeManager to load and save theme files, avoiding scattered string literals.
## Remarks
Centralizing the location of theme assets decouples theme storage from OS conventions and hard-coded paths, making future relocations or tests simpler. The static readonly nature guarantees a consistent path across all ThemeManager operations, computed at type initialization. If the target directory doesn't exist at runtime, higher-level startup or initialization code should ensure it is created before any read/write of themes.
## Notes
- Directory existence: ensure creation to avoid IO errors when reading or writing themes.
- Hidden folder nuance: .echohub will be hidden on Unix-like systems; consider how this affects user visibility or directory listings in certain UI scenarios.
---
### TransparentLightTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme TransparentLightTheme = new()
```
Represents a canonical light-theme configuration used by the UI to render surfaces on light backgrounds. TransparentLightTheme is a private static readonly Theme instance that bundles a complete color palette for Base, Menu, Dialog, Status, and Border, enabling a consistent light appearance across the UI when a light or transparent background is in use. The defined colors map foregrounds, backgrounds, and focus states to maintain readability and clear focus cues (Blue for focused elements).
## Remarks
By centralizing the light-theme palette in a single internal Theme instance, this symbol reduces drift between UI surfaces and makes it straightforward to derive alternate light variants from a single baseline. Its private visibility signals it's an internal default rather than a public customization point; external code should define and consume their own Theme instances instead of mutating this one.
## Notes
- Border foreground uses #8F8F8F for softer borders on light terminals.
- Background values set to 'None' indicate transparency or reliance on the parent/background, aligning with a transparent-light aesthetic.
---
### TransparentTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme TransparentTheme = new()
```
Defines a single, shared Theme instance named TransparentTheme that implements a glassy, semi-transparent UI aesthetic. Declared private static readonly, it is initialized once and reused by the ThemeManager to apply a cohesive translucent look across Base, Menu, Dialog, Status, and Border color groups (most backgrounds are None to preserve translucency, with White foreground and BrightCyan focus colors; Dialog uses DarkGray to retain legibility; borders use muted grays to complete the glassy look).
## Remarks
This symbol centralizes the glassy appearance so all UI surfaces adopting transparency share a single color model. Being private ensures the theme is an internal implementation detail of ThemeManager and not part of the public theming surface. If a project needs a similar variant publicly, it should be created as a separate, publicly accessible theme instance rather than exposing this private field. The pattern reduces drift between components and simplifies maintenance of the transparent aesthetic.
## Notes
- The field is readonly, but its nested color objects are not guaranteed immutable; mutating their properties at runtime would alter the shared theme for all users. Treat the instance as immutable after initialization to preserve consistency.
---
## BuiltInThemes
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly List<Theme> BuiltInThemes =
[
DefaultTheme,
TransparentTheme,
TransparentLightTheme,
ClassicTheme,
LightTheme,
HackerTheme,
SolarizedTheme,
DraculaTheme,
MonokaiTheme,
NordTheme,
GruvboxTheme,
OceanTheme,
HighContrastTheme,
RosePineTheme
]
```
BuiltInThemes is a private static readonly collection that enumerates the Theme instances shipped as built-in themes. It provides a stable, canonical set of themes (including DefaultTheme, TransparentTheme, TransparentLightTheme, ClassicTheme, LightTheme, HackerTheme, SolarizedTheme, DraculaTheme, MonokaiTheme, NordTheme, GruvboxTheme, OceanTheme, HighContrastTheme, and RosePineTheme) that ThemeManager can iterate over to present theme options and initialize theming state. Because the field is private and readonly, external code cannot modify this collection at runtime; it is intended as an internal baseline that ensures consistent theming behavior across the application.
## Remarks
Centralizes the shipped themes into a single place, guaranteeing a consistent ordering and a single source of truth for what counts as built-in. This reduces duplication and makes it easier to adjust defaults or add new themes by updating the initializer, rather than sprinkling Theme references throughout the code. Because it's private, consumers must rely on public Theme-related APIs or ThemeManager flows to query or apply themes.
## Notes
- The list is constructed from static Theme instances defined elsewhere (the DefaultTheme, TransparentTheme, etc.).
- As a private, readonly field, it cannot be replaced or mutated at runtime; new themes must be added via source changes.
- If you need to expose or customize the built-in set, provide a public API rather than accessing this field directly.
---
## GruvboxTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme GruvboxTheme = new()
```
GruvboxTheme is a private, static, readonly Theme instance that encodes the Gruvbox color palette for the EchoHub client UI. It defines colors for core regions—Base, Menu, Dialog, and Status—each with a Foreground, Background, FocusForeground, and FocusBackground value. This single object acts as the canonical Gruvbox styling source consumed by the theming subsystem to render a consistent look across the application. Because the field is private and readonly, external callers should rely on ThemeManager's public mechanisms to obtain themed resources rather than mutate or reference this field directly.
## Remarks
By centralizing the palette in one immutable object, GruvboxTheme reduces drift between UI regions and simplifies theming changes. The per-region color groups reflect a clean separation of concerns: Base handles the main chrome, Menu for navigation, Dialog for modal surfaces, and Status for status indicators; the consistent focus colors ensure accessible emphasis when keyboard navigation occurs. This pattern makes it straightforward to swap themes by replacing the underlying Theme instance without scattering color literals throughout the code.
## Notes
- The readonly reference prevents re-assignment, but if Theme or ThemeColors are mutable, their values can still be mutated at runtime.
- This field is private; there is no direct public API here—consumers should obtain theme data via ThemeManager's public surface rather than accessing GruvboxTheme directly.
---
## NordTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme NordTheme = new()
```
NordTheme defines the internal, immutable Nord color palette used by ThemeManager to style the UI. It is a single Theme instance configured with per-surface color mappings (Base, Menu, Dialog, Status) so the Nord look is applied consistently without duplicating color definitions throughout the code.
## Remarks
This symbol centralizes the Nord appearance, providing a single source of truth for foreground/background and focus colors across different UI surfaces. It is private to ThemeManager, which means external code should interact with the public theming API rather than reference or mutate this instance. The approach reduces drift between surfaces and makes it easy to switch themes by swapping higher-level theme providers rather than tweaking individual components.
## Notes
- Be aware that the readonly modifier applies to the field reference; nested ThemeColors instances may still be mutable if their properties expose setters. If true immutability is required, consider making Theme and ThemeColors immutable or returning defensive copies.
---
## RosePineTheme
> **File:** `src/EchoHub.Client/Themes/ThemeManager.cs`
> **Kind:** field
```csharp
private static readonly Theme RosePineTheme = new()
```
RosePineTheme is a private static readonly Theme instance that encapsulates the RosePine color palette used by the UI. It defines per-area color configurations for Base, Menu, Dialog, and Status, pairing foreground and background colors with their focused variants. This centralized definition provides a single source of truth for the RosePine look and is consumed by the theming subsystem rather than by external code, helping maintain a cohesive visual style across the application.
## Remarks
Centralizes theme-related color data to ensure visual consistency and to simplify theme swapping or adjustment. Keeping the field private hides implementation details from consumers and enforces usage through the theming infrastructure, reducing the risk of accidental divergence in color usage.
## Notes
- The field is private; external code cannot reference RosePineTheme directly.
- The field is readonly in reference, but its internal properties may be mutable depending on ThemeColors' mutability; if ThemeColors exposes setters, the palette could be modified after initialization.
- Static initialization order and potential side effects: If ThemeManager relies on RosePineTheme during application startup, ensure initialization order is correct.
---
@@ -0,0 +1,19 @@
# ChatColors
> **File:** `src/EchoHub.Client/UI/Chat/ChatColors.cs`
> **Kind:** class
```csharp
public static partial class ChatColors
```
Shared color attributes and a small parsing helper for chat rendering. Use this class when rendering chat UI elements (timestamps, system messages, mentions, channel references, embeds, attachments, etc.) so all parts of the UI use a consistent set of Attribute values. Call SplitMentions when you need to break a message into colored segments so mentions (@user) and channel references (#channel) can be rendered with their accent colors while non-special text uses a supplied default.
## Remarks
ChatColors centralizes the visual styling for chat components and includes a utility to split text into ChatSegment pieces that carry color information. SplitMentions performs a two-pass parse: first it finds @mentions (avoiding emails by requiring no preceding word character) and marks them with MentionTextAttr; then it examines the remaining, non-mention segments to find #channel references (the regex requires at least one letter to avoid matching hex colors or numeric issue references) and marks those with ChannelRefAttr. All Attribute instances are readonly and intended as shared, immutable style tokens that renderers can reuse.
## Notes
- The mention regex uses a negative lookbehind (?<!\w) so strings like "me@domain" are not treated as @mentions.
- The channel regex requires at least one ASCII letter to avoid matching plain hex colors or purely numeric tokens.
- SplitMentions accepts a nullable defaultColor; callers should handle null when rendering (null means "no explicit attribute supplied").
@@ -0,0 +1,120 @@
# ChatLine.cs
> **Source:** `src/EchoHub.Client/UI/Chat/ChatLine.cs`
## Contents
- [ChatLine](#chatline)
- [AttachmentActionSpan](#attachmentactionspan)
- [AttachmentAction](#attachmentaction)
---
## ChatLine
> **File:** `src/EchoHub.Client/UI/Chat/ChatLine.cs`
> **Kind:** class
```csharp
public partial class ChatLine
```
Represents a single rendered chat line made up of colored ChatSegment pieces and associated display metadata. Use ChatLine when preparing or manipulating a line for rendering in the chat view (layout, wrapping, attachment actions, separators, mention/highlight state) rather than working with raw strings or segments directly.
## Remarks
ChatLine is the view-level unit for a message or a rule separator: it aggregates ChatSegment instances (text + color), stores metadata such as MessageId, sender, attachment info and clickable action spans, and exposes logic to break the line into multiple display lines that fit a viewport width. It centralizes presentation concerns (continuation indentation, colored continuation prefixes, non-wrapping rule lines, and unread-marker behavior) so the chat rendering layer can ask a ChatLine to produce the wrapped pieces it needs rather than implementing wrapping and metadata handling itself.
## Example
```csharp
// Construct from plain text
var line = new ChatLine("Hello, world!");
// Optional metadata
line.MessageId = Guid.NewGuid();
line.SenderUsername = "alice";
// Wrap to a viewport width of 40 columns, with a 4-space continuation indent
var wrapped = line.Wrap(40, continuationIndent: 4);
// Construct from explicit segments (preserves per-segment color attributes)
var segments = new List<ChatSegment>
{
new ChatSegment("[alice] ", ChatColors.RailAttr),
new ChatSegment("This is a message", null)
};
var coloredLine = new ChatLine(segments);
```
## Notes
- RuleLabel makes the line a separator rule; such lines are not word-wrapped and are regenerated to the viewport width by the view.
- If ContinuationPrefixSegments is set, it overrides ContinuationIndent: continuation lines use the prefix segments' column width instead of plain-space indentation.
- ActionSpans (when present) are column positions relative to the unwrapped line; only the first wrapped line preserves those spans — subsequent wrapped continuation lines do not.
- Wrapping respects grapheme clusters and column widths (uses GetGraphemes and GetColumns), so wide characters and combining sequences are handled when measuring width. If width <= 0 or the line already fits, Wrap returns the original line in a single-element list.
---
## AttachmentActionSpan
> **File:** `src/EchoHub.Client/UI/Chat/ChatLine.cs`
> **Kind:** record
```csharp
public readonly record struct AttachmentActionSpan(int StartCol, int EndCol, AttachmentAction Action)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `StartCol` | `int` | — |
| `EndCol` | `int` | — |
| `Action` | `AttachmentAction` | — |
It encodes an inclusive horizontal span on a chat line that maps to an AttachmentAction when clicked. This readonly record struct pairs StartCol and EndCol (both inclusive) with an Action to designate a specific clickable region that triggers an attachment operation.
## Remarks
Because it's a value type with immutable fields, AttachmentActionSpan is cheap to copy and compare, which helps with hit-testing and rendering across frames. It expresses the intent of interactive regions alongside their coordinates and associated action, keeping the UI layer decoupled from how actions are executed. This symbol complements other line-rendering data structures that describe clickable spans, enabling straightforward collection, filtering, and application during rendering.
## Notes
- EndCol is inclusive; ensure range checks treat EndCol as inclusive to avoid off-by-one errors.
- Overlapping spans may require careful resolution logic at render or hit-test time to determine which action should fire.
---
## AttachmentAction
> **File:** `src/EchoHub.Client/UI/Chat/ChatLine.cs`
> **Kind:** enum
```csharp
public enum AttachmentAction
{
OpenImage,
SaveImage,
}
```
An enum that represents the action a click on an attachment line can trigger in the chat UI. It lets the click handler distinguish between opening the image for viewing and saving the image to disk, promoting explicit, testable logic rather than ad-hoc behavior.
## Remarks
By codifying the possible outcomes as an enum, AttachmentAction defines a clear contract for how attachment clicks should be handled. It decouples the UI event from the concrete actions, making it easy to extend with new options (for example, ShareImage) without changing call sites. This abstraction supports consistent behavior across different chat lines and simplifies testing by allowing mocks or verifications based on the enum value.
## Example
```csharp
AttachmentAction action = /* determined by UI context */;
switch (action)
{
case AttachmentAction.OpenImage:
// Open the image in a viewer
break;
case AttachmentAction.SaveImage:
// Persist the image to disk
break;
}
```
## Notes
- If you later add actions to the enum, remember to handle them in all switch expressions and tests.
- Prefer explicit enum-based logic over string-based representations to avoid misinterpretation.
- Ensure UI-to-action mappings are consistent across chat lines to prevent user confusion.
---
@@ -0,0 +1,19 @@
# ChatListSource
> **File:** `src/EchoHub.Client/UI/Chat/ChatListSource.cs`
> **Kind:** class
```csharp
public class ChatListSource : IListDataSource
```
A list-backed data source for chat messages that implements IListDataSource and performs grapheme-aware rendering with per-segment coloring, mention-background highlighting, and a focus-based full-row highlight. Use this when supplying chat messages to a ListView-like control that expects the data source to manage items, raise collection-change notifications, and draw each row with segment-level attributes and correct column clipping.
## Remarks
ChatListSource maintains an internal `List<ChatLine>`, tracks the longest item via MaxItemLength, and raises a CollectionChanged (Reset) event whenever the collection is modified unless SuspendCollectionChangedEvent is set. Its Render implementation is grapheme-aware (uses GraphemeHelper.GetGraphemes and each grapheme's column width) and applies attributes per ChatSegment: a Focus attribute (when the row is selected and the list has focus) or the segment's color with fallbacks for missing backgrounds. If a ChatLine.IsMention is true the renderer uses ChatColors.MentionHighlightAttr.Background to override segment backgrounds and to fill the remainder of the row.
## Notes
- GetLine returns null for out-of-range indices; callers should validate the index first.
- IsMarked/SetMark are intentionally no-ops in this implementation and Dispose is a no-op — no per-item mark state or unmanaged cleanup is performed.
- MaxItemLength is updated only when lines are added/inserted; mutating a ChatLine.TextLength after insertion will not update MaxItemLength automatically. Use SuspendCollectionChangedEvent to batch updates and suppress the Reset event during bulk changes.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
# ChatSegment
> **File:** `src/EchoHub.Client/UI/Chat/ChatSegment.cs`
> **Kind:** record
```csharp
public record ChatSegment(string Text, Attribute? Color)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Text` | `string` | — |
| `Color` | `Attribute?` | — |
ChatSegment represents a colored fragment of text within a chat line. It pairs the displayed text with an optional color attribute, enabling the UI to render parts of a message with varying styling without altering the textual content. As a record, ChatSegment is immutable and supports value-based equality, making it convenient to compose a full line by aggregating multiple segments in a deterministic way.
## Remarks
ChatSegment exists to separate content from presentation. By modeling a line as a sequence of segments, the rendering layer can apply different colors or styles to each piece while preserving the original order. The record-like semantics also ease comparisons, caching, and deduplication of segments across messages.
## Notes
- Color is stored as a nullable Attribute; a null Color means no special styling is requested for this segment.
- `Attribute` is a general metadata type; downstream renderers interpret it to apply styling. The exact meaning of the Color value depends on the consuming UI.
- Because ChatSegment is a two-property record, equality includes both Text and Color; changes to either produce a distinct segment, which is important when deduplicating or comparing segments.
@@ -0,0 +1,11 @@
# RenderHelpers
> **File:** `src/EchoHub.Client/UI/Chat/RenderHelpers.cs`
> **Kind:** class
```csharp
static class RenderHelpers
```
RenderHelpers is a small, shared utility for rendering IListDataSource content. Its WriteText method writes text to a ListView grapheme-by-grapheme while respecting a maximum width, returning the updated count of drawn columns. It iterates over grapheme clusters obtained from GraphemeHelper.GetGraphemes(text); for each grapheme, it computes the display width with GetColumns() (falling back to 1 if necessary). If adding the grapheme would exceed maxWidth, rendering stops. Otherwise, it appends the grapheme to the ListView via lv.AddStr(grapheme) and increments the drawn count. This centralizes grapheme-aware rendering logic so multiple IListDataSource implementations share consistent width handling and avoid duplicating rendering concerns.
@@ -0,0 +1,24 @@
# WelcomeBanner
> **File:** `src/EchoHub.Client/UI/Chat/WelcomeBanner.cs`
> **Kind:** class
```csharp
internal static class WelcomeBanner
```
Renders a MOTD-style splash in the chat pane when no channel is selected — a gold-gradient ASCII logo accompanied by a version tagline and quick usage hints, evoking classic IRC greetings. Use WelcomeBanner.Build to generate the banner lines for a given viewport width and version string, then feed those lines into the chat UI.
## Remarks
WelcomeBanner encapsulates the presentation of the welcome banner: centering, padding, colorization, and the two-logo strategy are all handled here so the rest of the chat UI can simply render a sequence of lines. It selects between a full-width BigLogo and a compact SmallLogo based on the viewport width, scales the gradient across the chosen logo, and appends a version tagline plus a set of user hints. This keeps branding consistent across sizes and isolates banner-specific formatting from the broader rendering pipeline.
## Example
```csharp
var lines = WelcomeBanner.Build(80, "1.2.3");
// integrate 'lines' into the chat pane
```
## Notes
- The logo variant is chosen based on the provided width; very small panes will display SmallLogo to preserve legibility.
- The color attributes (Attributes on ChatSegment) require UI support in the chat renderer; without color support the banner falls back to plain text.
@@ -0,0 +1,23 @@
# AudioPlayerDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/AudioPlayerDialog.cs`
> **Kind:** class
```csharp
public sealed class AudioPlayerDialog
```
AudioPlayerDialog is a sealed class that presents a modal Audio Player UI within the application's terminal UI. It assembles a compact layout with the current file name, a wave-like block visualization, playback status, and simple volume and playback controls, all exposed via a single Show method that binds an IApplication and an AudioPlaybackService to the dialog's lifecycle.
## Remarks
By encapsulating layout, colors, and animation in one place, it provides a reusable, cohesive UX for audio playback that can be dropped into different screens without duplicating UI code. The class relies on themed attributes (e.g. WaveActiveAttr, WaveIdleAttr, FileNameAttr, Status*Attr) to ensure consistent appearance, and uses a timer-driven animation loop to render the wave pattern while playback is active.
## Example
```csharp
AudioPlayerDialog.Show(app, audioService, "/path/to/song.mp3", "song.mp3");
```
## Notes
- The waveform visualization uses Unicode block characters; ensure your terminal font supports these glyphs for correct rendering.
- The dialog starts a background animation timer; dispose the dialog or stop the timer to avoid leaks when closing.
@@ -0,0 +1,23 @@
# ChannelPasswordDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/ChannelPasswordDialog.cs`
> **Kind:** class
```csharp
public sealed class ChannelPasswordDialog
```
Prompts for a channel password when joining a protected channel and returns the entered password, or null if the user cancels. Use this helper whenever you need a consistent, modal password prompt instead of duplicating dialog boilerplate across join flows.
## Remarks
This class centralizes the user flow for joining password-protected channels. It presents a modal dialog titled Join #<channel>, collects the password, and returns it to the caller, ensuring a single, predictable contract. The UI avoids displaying the actual password text by using a redacted caption and automatically focusing the password field, while the dialog lifecycle is orchestrated through the application (app.Run and app.RequestStop).
## Example
```csharp
string? password = ChannelPasswordDialog.Show(app, "mychannel", "Enter password to join #mychannel.");
```
## Notes
- The method is synchronous and modal; it blocks the caller until the user completes the interaction.
- A null return value indicates the user canceled the operation. If the user submits an empty password, a brief error dialog is shown and the prompt remains active until a non-empty password is provided.
@@ -0,0 +1,80 @@
# ConnectDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/ConnectDialog.cs`
## Contents
- [ConnectDialog](#connectdialog)
- [ConnectDialogResult](#connectdialogresult)
---
## ConnectDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/ConnectDialog.cs`
> **Kind:** class
```csharp
public sealed class ConnectDialog
```
ConnectDialog is a Terminal.Gui-based dialog that collects server connection details and authentication information for the application. When shown, it can display a list of SavedServer entries at the top if any saved servers are provided; in that case a Saved Servers section is rendered with a ListView of display names that indicate whether a session exists (the code appends a [session] marker when a RefreshToken is present). Below (or in place of it, when there are no saved servers), the dialog presents manual entry fields for Server URL (default http://localhost:5000), Username, and Password, along with UI hints such as a hidden password placeholder and a Remember me option. Additional fields include Display Name and, when relevant, an Invite Code for invite-gated registrations. The static Show method returns a ConnectDialogResult when the user completes the dialog, or null if the dialog is cancelled; the dialog height is adjusted depending on whether saved servers are shown.
---
## ConnectDialogResult
> **File:** `src/EchoHub.Client/UI/Dialogs/ConnectDialog.cs`
> **Kind:** record
```csharp
public record ConnectDialogResult(
string ServerUrl, string Username, string Password,
bool IsRegister, bool RememberMe, string? SavedRefreshToken,
string? DisplayName = null, string? InviteCode = null)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `ServerUrl` | `string` | — |
| `Username` | `string` | — |
| `Password` | `string` | — |
| `IsRegister` | `bool` | — |
| `RememberMe` | `bool` | — |
| `SavedRefreshToken` | `string?` | — |
| `DisplayName` | `string?` | `null` |
| [`InviteCode`](../../../EchoHub.Core/Models/InviteCode.cs.md) | `string?` | `null` |
ConnectDialogResult encapsulates all user input gathered from the connect dialog as a single, immutable value. It is produced when the dialog completes and is consumed by the rest of the application to initiate a connection flow, passing the server URL, credentials, and onboarding flags as a single, strongly-typed package.
## Remarks
By collecting all related fields into a single record, this abstraction reduces coupling between the UI layer and the connection logic. It clearly expresses the intent of the user's action (login vs register) and whether credentials should be remembered, while allowing optional data (DisplayName, InviteCode) to participate in specialized flows without forcing callers to thread every field separately.
## Example
```csharp
// Common usage: construct a result from values collected in UI
var result = new ConnectDialogResult(
ServerUrl: "https://example.server/api",
Username: "alice",
Password: "P@ssw0rd",
IsRegister: false,
RememberMe: true,
SavedRefreshToken: null,
DisplayName: "Alice",
InviteCode: "INVITE-2024-ABCD"
);
```
## Notes
- DisplayName and InviteCode are nullable; omit them or pass null if not applicable.
- Password should be treated as sensitive data: avoid logging it or persisting it longer than necessary, and ensure proper disposal or clearing after use.
- SavedRefreshToken may be null; handle accordingly in login/refresh flows.
- This record is intended for in-memory transfer between UI and authentication/connection logic; when persisting or transmitting, apply appropriate security measures and avoid leaking confidential fields.
---
@@ -0,0 +1,74 @@
# CreateChannelDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/CreateChannelDialog.cs`
## Contents
- [CreateChannelDialog](#createchanneldialog)
- [CreateChannelResult](#createchannelresult)
---
## CreateChannelDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/CreateChannelDialog.cs`
> **Kind:** class
```csharp
public sealed class CreateChannelDialog
```
Displays a modal Create Channel dialog that collects the details needed to create a new channel: a name, an optional topic, a password, and a public visibility setting. The name is trimmed and normalized to lower case; if it is empty, the dialog reports an error and stays open. On Create, it builds a CreateChannelResult containing the name, topic (nullable), isPublic, and the password; on Cancel it returns null. The dialog runs via the provided IApplication instance and returns after the user makes a choice.
## Remarks
Encapsulates all UI logic for channel creation into a single entry point, enabling consistent behavior across the app and isolating rendering from business logic. The class acts as a small, self-contained UX widget that constructs the result object, ensuring callers need only handle the CreateChannelResult or null.
## Notes
- Name validation is minimal in code: the name is trimmed and lowercased, and non-empty; there is no explicit enforcement of length or allowed character patterns at runtime beyond what the UI hints suggest.
- Password handling appears behind-the-scenes (the UI labels redact the password, yet the password value is captured and returned as part of the result); ensure secure handling and minimize exposure of the plaintext password.
- The snippet references passwordField and publicCheckbox, which must exist in the full class scope; if you modify the UI composition, ensure these controls are present and wired consistently with the password retrieval and public visibility logic.
---
## CreateChannelResult
> **File:** `src/EchoHub.Client/UI/Dialogs/CreateChannelDialog.cs`
> **Kind:** record
```csharp
public record CreateChannelResult(string Name, string? Topic, bool IsPublic, string? Password)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Name` | `string` | — |
| `Topic` | `string?` | — |
| `IsPublic` | `bool` | — |
| `Password` | `string?` | — |
CreateChannelResult is an immutable data carrier that represents the outcome of creating a channel in the EchoHub client UI. It carries the channel's Name, an optional Topic, a flag IsPublic indicating whether the channel is public, and an optional Password.
## Remarks
As a record, CreateChannelResult participates in value-based equality, making comparisons straightforward without manual field checks. The positional constructor provides a concise, immutable payload that is easy to pass through layers (UI, services, or view models). You can deconstruct a result into its components, or derive a modified copy with a with-expression if you need a slightly different result without mutating the original. This type is intended to be produced by the channel-creation flow and consumed by UI code and downstream components.
## Example
```csharp
// Common case: create a public channel with a topic and password
var result = new CreateChannelResult("General", "Team discussions", true, "s3cr3t");
// Access fields
string name = result.Name;
string? topic = result.Topic;
bool isPublic = result.IsPublic;
string? password = result.Password;
// Deconstruct for convenience
var (n, t, pub, pwd) = result;
// Create a modified copy
var updated = result with { Topic = "New topic" };
```
---
@@ -0,0 +1,64 @@
# ProfileEditDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/ProfileEditDialog.cs`
## Contents
- [ProfileEditDialog](#profileeditdialog)
- [ProfileEditResult](#profileeditresult)
---
## ProfileEditDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/ProfileEditDialog.cs`
> **Kind:** class
```csharp
public sealed class ProfileEditDialog
```
ProfileEditDialog provides a Terminal.Gui dialog for editing the user's profile. Its Show method presents a modal dialog titled "Edit Profile" with fields for Display Name, Bio, Nickname Color (with a hex input and a live color preview) and Avatar selection, plus notification preferences, returning a ProfileEditResult when the user accepts or null if cancelled.
## Remarks
ProfileEditDialog centralizes profile-edit UI in one reusable component, ensuring a consistent look and behavior whenever the user updates their profile. It wires up real-time color previews by updating the color swatch whenever the hex input changes, and it delegates color parsing to HexColorHelper to translate user input into a Color value. The Avatar field demonstrates integration with a file picker (OpenDialog) within a Terminal.Gui workflow, keeping file selection cohesive with the rest of the dialog.
## Notes
- The Show method accepts optional parameters for notificationSoundEnabled and notificationVolume, defaulting to false and 30 respectively.
- If no avatar is selected, avatarField.Text remains empty.
- The return type is ProfileEditResult?; callers should handle null to cover the cancel path.
- This implementation relies on Terminal.Gui primitives (Label, TextField, Button, CheckBox, OpenDialog) and collaborator types (ProfileEditResult, HexColorHelper); ensure these types are available in the consuming project.
---
## ProfileEditResult
> **File:** `src/EchoHub.Client/UI/Dialogs/ProfileEditDialog.cs`
> **Kind:** record
```csharp
public record ProfileEditResult(string? DisplayName, string? Bio, string? NicknameColor, string? AvatarPath, bool? NotificationSoundEnabled, byte? NotificationVolume)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `DisplayName` | `string?` | — |
| `Bio` | `string?` | — |
| `NicknameColor` | `string?` | — |
| `AvatarPath` | `string?` | — |
| `NotificationSoundEnabled` | `bool?` | — |
| `NotificationVolume` | `byte?` | — |
Represents the data returned from the profile edit dialog. It encapsulates the user\'s optional inputs for DisplayName, Bio, NicknameColor, AvatarPath, NotificationSoundEnabled, and NotificationVolume so the caller can apply changes in a single operation. Each property is nullable: a null value means no change for that field; a non-null value provides a new value to persist.
## Remarks
ProfileEditResult is an immutable value object used as the dialog\'s return type. Its nullable fields express a delta: non-null values indicate updates, while null indicates no change. As a record, it benefits from value-based equality, making comparisons and tests straightforward, and it cleanly separates UI input from downstream update logic.
## Notes
- Null values indicate no change; apply only non-null fields when updating the profile.
- The type is immutable; to derive modifications, use a with-expression to create a new instance.
---
@@ -0,0 +1,51 @@
# ProfileViewDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/ProfileViewDialog.cs`
## Contents
- [ProfileViewDialog](#profileviewdialog)
- [ProfileAction](#profileaction)
---
## ProfileViewDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/ProfileViewDialog.cs`
> **Kind:** class
```csharp
public sealed class ProfileViewDialog
```
ProfileViewDialog renders a dialog to view a user's server profile; when showing the current user's profile it also exposes action buttons (Edit Profile / Set Status) and returns the chosen ProfileAction, while viewing another user yields a read-only presentation.
## Remarks
ProfileViewDialog encapsulates all the layout and formatting decisions for a user profile in a single place. It dynamically switches between a read-only view and an ownership-aware view that surfaces actions, and it applies color theming to the status and nickname fields. By centralizing this UI behavior, the dialog remains consistent across the application and reduces duplication by isolating profile presentation from business logic. The component gracefully handles a missing profile by showing an error message and returning a Close action, which defines a clear contract for callers.
## Notes
- If invoked with a null profile, the dialog shows an error and returns ProfileAction.Close; callers should guard against null input or handle the Close result accordingly.
- The dialog title differentiates ownership with "My Profile" for the current user and "Profile — {username}" for others, and it uses color-coding helpers to reflect status and nickname color for quick visual cues.
---
## ProfileAction
> **File:** `src/EchoHub.Client/UI/Dialogs/ProfileViewDialog.cs`
> **Kind:** enum
```csharp
public enum ProfileAction
{
Close,
EditProfile,
SetStatus
}
```
ProfileAction defines the set of actions a user can select from their profile dialog: Close, EditProfile, and SetStatus. It provides a typed representation of user intent that downstream UI logic can handle in a deterministic way, rather than relying on magic strings or numeric codes.
## Remarks
ProfileAction represents the users chosen action from the profile dialog, allowing the UI layer to dispatch the appropriate workflow in a type-safe way. By enumerating possible intents, the code can exhaustively handle all cases in a switch or pattern-match, reducing errors from invalid values. The Close action also clarifies that the action is about dialog lifecycle control as opposed to in-dialog tasks such as editing or setting status. If new actions are required in the future, they should be added here with clear naming that maps to corresponding UI behaviors.
---
@@ -0,0 +1,94 @@
# SearchDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/SearchDialog.cs`
## Contents
- [SearchDialog](#searchdialog)
- [SearchResult](#searchresult)
- [SearchResultType](#searchresulttype)
---
## SearchDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/SearchDialog.cs`
> **Kind:** class
```csharp
public static class SearchDialog
```
SearchDialog is a command-palette style search dialog used to quickly navigate channels and trigger common app actions from a single, keyboard-driven interface. Use it when you want fast, non-mouse access to channels and actions by filtering a combined list and selecting with Enter.
## Remarks
SearchDialog composes a modal dialog that presents both channel names and a predefined set of actions, merged into a single searchable list via a SearchListSource. It returns the selected SearchResult and signals completion to the hosting application by invoking RequestStop on IApplication, keeping the dialog logic decoupled from the rest of the UI. This abstraction enables a reusable, consistent navigation surface across different parts of the app.
## Example
```csharp
// Example
IApplication app = /* obtain your app instance */;
IReadOnlyList<string> channels = new[] { "general", "engineering" };
var result = SearchDialog.Show(app, channels);
if (result != null)
{
// Handle the selected item (channel or action) here.
}
```
## Notes
- The dialog includes a hint, a text field for filtering, a list of results, and a Cancel button; selection is returned as a SearchResult, or null if cancelled.
- Ctrl+K handling in both the dialog and the search field cancels the operation by requesting stop from the application, so be aware that this combo acts as a cancel gesture rather than an open/search trigger.
- When items exist, the first item is pre-selected; filtering updates the source and may reset the selection.
---
## SearchResult
> **File:** `src/EchoHub.Client/UI/Dialogs/SearchDialog.cs`
> **Kind:** record
```csharp
public record SearchResult(SearchResultType Type, string Key, string Label)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Type` | `SearchResultType` | — |
| `Key` | `string` | — |
| `Label` | `string` | — |
Represents a single entry in search results, encapsulating the result's category (Type), a key (Key), and a user-facing label (Label). As a positional-record, it is immutable and compared by value, which makes it convenient to pass around and render in the search UI.
## Remarks
Use SearchResult to model a single outcome returned by the search feature. Type communicates the kind of item (as defined by SearchResultType), Key is the stable identifier for navigation or lookup, and Label is the display text shown in the results list. Because it is a deconstructible record, you can conveniently extract its fields with deconstruction or pattern matching, and equality checks are based on the content rather than the instance identity.
## Notes
- Immutability: SearchResult uses a primary constructor; properties are read-only and a modified instance must be created with a with-expression or a new constructor.
- Deconstruction: The positional constructor enables deconstruction: var (t, k, l) = result; or access via result.Type, result.Key, result.Label.
- Type relies on the SearchResultType enum; when consuming code, prefer switching on Type rather than comparing display strings.
---
## SearchResultType
> **File:** `src/EchoHub.Client/UI/Dialogs/SearchDialog.cs`
> **Kind:** enum
```csharp
public enum SearchResultType
{
Channel,
Action
}
```
Represents the category of a search result in the EchoHub client UI, distinguishing Channel results from Action results. Developers reach for this enum to branch rendering or navigation logic based on the result type, instead of using boolean flags or string comparisons.
## Remarks
Because it is a small discriminant, SearchResultType is typically consumed alongside a broader SearchResult structure. It enables simple pattern matching in switch expressions or if statements, guiding UI decisions such as which view to open or which icon to display when a user selects a result.
---
@@ -0,0 +1,69 @@
# StatusDialog.cs
> **Source:** `src/EchoHub.Client/UI/Dialogs/StatusDialog.cs`
## Contents
- [StatusDialog](#statusdialog)
- [StatusDialogResult](#statusdialogresult)
---
## StatusDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/StatusDialog.cs`
> **Kind:** class
```csharp
public sealed class StatusDialog
```
StatusDialog is a terminal-based UI component that presents a compact dialog for updating the current user's status and an optional status message. Its Show method renders the dialog initialized with the provided current status and message, and returns a StatusDialogResult when the user saves, or null if the user cancels.
The dialog consists of a title 'Set Status', a status option selector pre-populated with the current status, a text field for the status message, and Save/Cancel actions. On Save, the selected status is captured (defaulting to Online if nothing is selected) and the message is trimmed; an empty message becomes null. The method returns a new StatusDialogResult with those values and stops the application loop via app.RequestStop(); Cancel returns null and stops the loop.
Callers use the returned result to apply the updated status and message; otherwise, no changes are made.
## Remarks
StatusDialog encapsulates the presentation logic for updating user status, isolating UI concerns from business logic. It is a small, reusable piece that orchestrates Terminal.Gui controls (Dialog, Label, OptionSelector, TextField, Button) and relies on IApplication to drive the modal flow. The use of a default Online and trimming of the message ensures sane behavior even when fields are left blank.
## Example
```csharp
var result = StatusDialog.Show(app, currentStatus, currentMessage);
if (result != null)
{
// Apply updates to the user's status and message
currentStatus = result.Status;
currentMessage = result.Message;
}
```
## Notes
- A null result indicates the user cancelled the dialog; callers should guard against applying changes in this case.
- If the user leaves the Message field blank or whitespace, the message is stored as null.
- The Save action is wired as the default action (IsDefault = true), and both Save and Cancel terminate the modal interaction by invoking app.RequestStop().
---
## StatusDialogResult
> **File:** `src/EchoHub.Client/UI/Dialogs/StatusDialog.cs`
> **Kind:** record
```csharp
public record StatusDialogResult(UserStatus Status, string? StatusMessage)
```
**Parameters:**
| Parameter | Type | Default |
|-----------|------|---------|
| `Status` | [`UserStatus`](../../../EchoHub.Core/Models/UserStatus.cs.md) | — |
| `StatusMessage` | `string?` | — |
StatusDialogResult is a minimal, immutable data carrier returned when the status dialog completes. It groups the chosen user status (Status) with an optional message (StatusMessage) into a single value that downstream logic can consume without inspecting the dialog UI directly. As a C# record, it benefits from value-based equality and straightforward deconstruction.
## Remarks
StatusDialogResult encapsulates the outcome of a UI interaction into a single semantic unit that can be passed through the application flow or stored for auditing. It separates presentation concerns from business logic: callers reason about the user's status and optional message rather than UI details. The nullable StatusMessage signals that extra context is optional; consumer code should handle the absence gracefully, typically by pattern matching on Status and checking for a non-null message. The record type also supports structural equality, making tests and comparisons concise.
---
@@ -0,0 +1,14 @@
# UpdateConfirmDialog
> **File:** `src/EchoHub.Client/UI/Dialogs/UpdateConfirmDialog.cs`
> **Kind:** class
```csharp
public sealed class UpdateConfirmDialog
```
UpdateConfirmDialog is a sealed utility with a single static Show method that prompts the user to confirm an available update. It builds a small modal dialog titled “Update Available” showing the current and latest versions and offers two actions: Update (default) and Cancel; it returns true if the user chooses Update and false otherwise. The method runs the provided IApplication until the user makes a choice, using RequestStop to close the dialog and return the result.
## Remarks
It encapsulates the update-confirmation interaction as a reusable, modal prompt that coordinates with the host application's event loop, avoiding duplication of dialog boilerplate across the codebase.
@@ -0,0 +1,28 @@
# DroppedFileParser
> **File:** `src/EchoHub.Client/UI/Helpers/DroppedFileParser.cs`
> **Kind:** class
```csharp
public static class DroppedFileParser
```
DroppedFileParser is a small utility that interprets terminal-dropped input as potential file paths and resolves them to existing files. Use it when you need to convert user-typed or pasted text into concrete file paths without scattering filesystem checks across callers.
## Remarks
This abstraction centralizes the logic for recognizing path-like input and for extracting one or more existing file paths from either a single path or a space-separated list of paths. It exposes a fast pre-check (LooksLikePath) to avoid expensive filesystem calls for clearly non-path input, and a test-friendly parser (TryGetFiles) that can inject a custom file existence predicate. The design favors explicit handling of both Windows (drive letters and UNC) and POSIX-style absolute paths, including quoted components and spaces.
## Example
```csharp
var input = "\"C:\\Temp\\report.pdf\" C:\\Data\\log.txt";
if (DroppedFileParser.TryGetFiles(input, out var files))
{
// files contains: ["C:\\Temp\\report.pdf", "C:\\Data\\log.txt"]
}
```
## Notes
- LookSLikePath may return true for strings that resemble paths (e.g., starting with a quote, a slash, UNC prefix, or a drive letter), so TryGetFiles should be used to confirm actual file existence.
- TryGetFiles enforces that all tokens are fully-qualified paths and that each path exists (via the injectable fileExists predicate, which defaults to File.Exists). This reduces accidental assumptions about the input.
- The tokenization logic respects quoted segments so that spaces within a single path do not split tokens unintentionally.
@@ -0,0 +1,19 @@
# EmojiHelper
> **File:** `src/EchoHub.Client/UI/Helpers/EmojiHelper.cs`
> **Kind:** class
```csharp
public static class EmojiHelper
```
EmojiHelper converts emoji grapheme clusters to text shortcodes for safe TUI rendering. It replaces emoji with fixed-width ASCII shortcodes when available, falling back to a generic [emoji] placeholder for unknown symbols; non-emoji text passes through unchanged.
## Remarks
This utility uses grapheme-aware processing to handle complex emoji sequences (including ZWJ-joined glyphs and modifier-bearing emojis) by iterating over text elements rather than individual code points. It first attempts a full-grapheme shortcode lookup, then falls back to the base emoji (the first rune of the grapheme) if necessary, and finally uses the [emoji] placeholder when no mapping exists. An initial pass quickly determines whether any emoji exist in the input to avoid unnecessary work. The implementation relies on StringBuilder for efficient string construction, StringInfo for grapheme segmentation, and the EmojiShortcodes mapping as the source of truth for replacements.
## Notes
- Unknown or unmapped emoji are replaced with [emoji], which can reduce expressiveness if the shortcode dictionary is incomplete. Ensure EmojiShortcodes covers the emoji you expect to render in your UI.
@@ -0,0 +1,18 @@
# HexColorHelper
> **File:** `src/EchoHub.Client/UI/Helpers/HexColorHelper.cs`
> **Kind:** class
```csharp
public static class HexColorHelper
```
HexColorHelper is a small utility that converts hex color strings into Terminal.Gui coloring primitives. Use ParseHexColor to obtain an Attribute suitable for styling a control's foreground, and ParseHexToColor when you need a Color value with a safe fallback for invalid input.
## Remarks
By centralizing hex parsing, HexColorHelper avoids duplicating color-conversion logic and provides predictable fallbacks for malformed input. It interprets a hex string as an RGB triplet and applies it as the foreground color (with no explicit background). This keeps styling decisions consistent across the UI while keeping the parsing logic isolated in one place.
## Notes
- Invalid input yields null (for ParseHexColor) or the provided fallback (for ParseHexToColor); no exceptions are thrown.
- A 6-digit hex value is required after an optional leading '#'. Non-hex characters or incorrect length return fallback/null.
@@ -0,0 +1,25 @@
# NickColorHelper
> **File:** `src/EchoHub.Client/UI/Helpers/NickColorHelper.cs`
> **Kind:** class
```csharp
public static class NickColorHelper
```
NickColorHelper deterministically assigns a stable color to every nickname, ensuring the same nick always maps to the same palette entry. This mirrors classic IRC behavior and lets busy channels stay readable without per-user configuration.
## Remarks
NickColorHelper isolates color selection from rendering logic by exposing a pure function GetPaletteIndex and GetAttribute. The palette itself is a fixed sequence of medium-saturation colors designed for legibility on both dark and light backgrounds; changing the palette order would re-color every nick and break visual consistency across sessions.
## Example
```csharp
var color = NickColorHelper.GetAttribute("Alice");
// Use `color` when rendering Alice's username in the UI
```
## Notes
- Null nick will throw; ensure non-null before calling GetPaletteIndex.
- The palette order is fixed; reordering or removing entries changes every nickname's color.
- The mapping uses a case-insensitive FNV-1a hash; changing the hash or its normalization will alter which nick gets which color.
@@ -0,0 +1,21 @@
# ChannelListSource
> **File:** `src/EchoHub.Client/UI/ListSources/ChannelListSource.cs`
> **Kind:** class
```csharp
public class ChannelListSource : IListDataSource
```
A colored, list-backed IListDataSource that presents channel names with visual affordances: an active-channel indicator, unread count badges, and markers for protected, private and system channels. Use this when you need a ListView-compatible data source that maintains channel ordering, per-channel unread counts and simple visual state (active, mention, protected/private, system) instead of hand-rendering each row.
## Remarks
ChannelListSource centralizes the channel-list state required by a ListView: the ordered channel names, a per-channel unread count map and several role sets (protected, mention, private and system). It exposes a single Update method that replaces the in-memory collections in one operation and (unless suspended) raises a Reset collection-changed event so consumers can re-layout or refresh. The class also provides MaxItemLength to help the host compute layout and ToList to produce a display-friendly list of channel strings (each prefixed with '#'). Rendering is delegated to the ListView via the Render method; the class supplies attributes (ActiveAttr, UnreadAttr, NormalAttr, BadgeAttr, MentionAttr, SystemAttr) and simple prefix/marker rules so the view paints active items, unread badges and visual separation for system channels.
## Notes
- Update clears and replaces all internal collections; call it with the full desired state rather than trying to patch individual entries.
- The Count/MaxItemLength values are derived from the current channel list. MaxItemLength computes name.Length + 6 (reserved space for prefixes/badges), so layout logic should consider that padding when sizing the list column.
- CollectionChanged will be invoked with a NotifyCollectionChangedAction.Reset at the end of Update unless SuspendCollectionChangedEvent is true. SuspendCollectionChangedEvent is a simple in-memory flag — using it prevents the Reset event from being raised during an Update.
- IsMarked and SetMark are intentionally inert (IsMarked always returns false and SetMark is a no-op), so callers should not rely on marking support from this source.
- Render moves the ListView cursor using Math.Max(col - viewportX, 0) to account for horizontal scrolling (viewportX). Hosts should provide correct viewportX and width values so rendering and clipping behave as intended.
@@ -0,0 +1,22 @@
# SearchListSource
> **File:** `src/EchoHub.Client/UI/ListSources/SearchListSource.cs`
> **Kind:** class
```csharp
public class SearchListSource(List<SearchResult> items) : IListDataSource
```
List data source that feeds a search dialog's ListView: it maintains an original item list, supports case-insensitive filtering by label or key, raises a Reset collection-changed notification when the filter changes (unless suspended), and renders each row with color-coding depending on the SearchResultType.
## Remarks
This class combines two responsibilities commonly needed by a search dialog: fast, in-memory filtering of a fixed set of SearchResult records and rendering of those results into a ListView with per-type coloring. Consumers attach to CollectionChanged to refresh the UI when Filter(string) updates the visible set. Render uses RenderHelpers.WriteText to draw the label and then fills the remainder of the column; it chooses a highlight (selected) attribute from the ListView or a per-result attribute (channel/action) and preserves the list's background when a per-result attribute leaves the background as Color.None.
## Notes
- Filter is case-insensitive and matches either SearchResult.Label or SearchResult.Key.
- When Filter receives a null/whitespace query the visible list is reset to all items and a Reset event is raised (unless SuspendCollectionChangedEvent is true).
- IsMarked and SetMark are no-ops; this data source does not track per-item marks.
- Dispose is a no-op; there are no unmanaged resources to release.
- MaxItemLength returns 0 when there are no filtered items.
- This class does not provide internal synchronization; callers should ensure thread-safety when mutating the source list or calling Filter from multiple threads.
@@ -0,0 +1,21 @@
# UserListSource
> **File:** `src/EchoHub.Client/UI/ListSources/UserListSource.cs`
> **Kind:** class
```csharp
public class UserListSource : IListDataSource
```
A data source implementation for a list view that presents online users with per-user nickname colors. Use this when you need a ready-made IListDataSource that holds tuples of display text, an optional nickname color (Attribute), and the username; it supplies item count, a maximal item width, batch updates via Update, and a Render implementation that paints a status/prefix in the normal role and the username portion in the configured nickname color while respecting selection and a fixed column width.
## Remarks
UserListSource is a UI-focused data source: it couples a small in-memory collection of user display tuples with a Render method tailored for a ListView consumer. It delegates grapheme-aware splitting to GraphemeHelper so prefix characters (status icon and optional role badge) are drawn in the list's normal attribute while the visible username text is drawn in the per-user nickname color unless the item is selected (selection forces the normal/Focus attribute). MaxItemLength is maintained as a convenience for layout calculations and is updated by Update.
## Notes
- Update replaces the entire contents; after calling Update the class raises NotifyCollectionChangedAction.Reset unless SuspendCollectionChangedEvent is true. If you set SuspendCollectionChangedEvent to batch multiple updates you are responsible for raising/triggering an appropriate collection changed notification afterward.
- MaxItemLength is computed using each entry's Text.GetColumns(), so wide characters and grapheme clusters affect reported width — MaxItemLength is a column/terminal-width measure, not a character count.
- Rendering is grapheme-aware and respects the provided width: text drawing stops when the accumulated column width reaches the requested width. This prevents partial grapheme rendering but means long names will be truncated to fit.
- Several IListDataSource members are intentionally trivial: IsMarked and SetMark are no-ops, ToList returns the visible Text values as objects, and Dispose is a no-op. Callers should not rely on any persistent marking or disposal behavior from this class.
- The implementation contains no internal synchronization; it is not inherently thread-safe. Ensure all access (especially Update and Render) is serialized by the caller when used from multiple threads.
File diff suppressed because it is too large Load Diff