diff --git a/Controls/AppLogView.xaml b/Controls/AppLogView.xaml
index 73691c9..310d2b4 100644
--- a/Controls/AppLogView.xaml
+++ b/Controls/AppLogView.xaml
@@ -10,6 +10,10 @@
Text="Clear"
Order="Secondary"
Clicked="OnClearClicked" />
+
SetValue(ShowClearButtonProperty, value);
}
+ ///
+ /// Whether to show the Copy for AI toolbar item. Default: true.
+ ///
+ public static readonly BindableProperty ShowCopyForAIButtonProperty = BindableProperty.Create(
+ nameof(ShowCopyForAIButton),
+ typeof(bool),
+ typeof(AppLogView),
+ true,
+ propertyChanged: (b, o, n) => ((AppLogView)b).CopyForAIToolbarItem.IsEnabled = (bool)n);
+
+ ///
+ /// Gets or sets whether the Copy for AI toolbar button is visible.
+ ///
+ public bool ShowCopyForAIButton
+ {
+ get => (bool)GetValue(ShowCopyForAIButtonProperty);
+ set => SetValue(ShowCopyForAIButtonProperty, value);
+ }
+
///
/// Whether to automatically refresh when new items are added to the TelemetryClient. Default: false.
/// When enabled, polls for new items every 2 seconds.
@@ -186,6 +205,11 @@ public partial class AppLogView : ContentPage
///
public event EventHandler? LogsRefreshed;
+ ///
+ /// Raised when logs are copied for AI debugging.
+ ///
+ public event EventHandler? LogsCopiedForAI;
+
#endregion
#region Public Methods
@@ -313,6 +337,123 @@ public partial class AppLogView : ContentPage
return sb.ToString();
}
+ ///
+ /// Export logs in a format optimized for AI debugging assistance.
+ /// Includes system context, environment info, and structured log data.
+ ///
+ /// AI-optimized log export string.
+ public string ExportLogsForAI()
+ {
+ var sb = new StringBuilder();
+
+ // Header for AI
+ sb.AppendLine("# Application Debug Log for AI Analysis");
+ sb.AppendLine();
+ sb.AppendLine("Please analyze the following application logs and help identify issues, root causes, and potential fixes.");
+ sb.AppendLine();
+
+ // System Context
+ sb.AppendLine("## Environment");
+ sb.AppendLine("```");
+ sb.AppendLine($"Platform: {DeviceInfo.Platform}");
+ sb.AppendLine($"OS Version: {DeviceInfo.VersionString}");
+ sb.AppendLine($"Device: {DeviceInfo.Model} ({DeviceInfo.Manufacturer})");
+ sb.AppendLine($"Device Type: {DeviceInfo.DeviceType}");
+ sb.AppendLine($"App: {AppInfo.Name} v{AppInfo.VersionString} (Build {AppInfo.BuildString})");
+ sb.AppendLine($"Framework: .NET MAUI");
+ sb.AppendLine($"Captured: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
+ sb.AppendLine($"Local Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
+ sb.AppendLine($"Timezone: {TimeZoneInfo.Local.DisplayName}");
+ sb.AppendLine("```");
+ sb.AppendLine();
+
+ // Summary
+ var exceptions = _logItems.Where(i => i.Type == "exception").ToList();
+ var warnings = _logItems.Where(i => i.Type == "warning").ToList();
+ var journeyEvents = _logItems.Where(i => i.Type.StartsWith("journey_") || i.Type.StartsWith("step_")).ToList();
+
+ sb.AppendLine("## Summary");
+ sb.AppendLine($"- **Total Entries:** {_logItems.Count}");
+ sb.AppendLine($"- **Exceptions:** {exceptions.Count}");
+ sb.AppendLine($"- **Warnings:** {warnings.Count}");
+ sb.AppendLine($"- **Journey Events:** {journeyEvents.Count}");
+ if (_logItems.Count > 0)
+ {
+ var oldest = _logItems.Min(i => i.Timestamp);
+ var newest = _logItems.Max(i => i.Timestamp);
+ sb.AppendLine($"- **Time Range:** {oldest:HH:mm:ss} to {newest:HH:mm:ss} UTC ({(newest - oldest).TotalSeconds:F1}s span)");
+ }
+ sb.AppendLine();
+
+ // Exceptions first (most important for debugging)
+ if (exceptions.Count > 0)
+ {
+ sb.AppendLine("## Exceptions");
+ sb.AppendLine();
+ foreach (var item in exceptions.OrderByDescending(i => i.Timestamp))
+ {
+ sb.AppendLine($"### {item.Title}");
+ sb.AppendLine($"**Time:** {item.Timestamp:yyyy-MM-dd HH:mm:ss} UTC");
+ if (!string.IsNullOrEmpty(item.JourneyId))
+ sb.AppendLine($"**Journey ID:** {item.JourneyId}");
+ if (!string.IsNullOrEmpty(item.UserId))
+ sb.AppendLine($"**User ID:** {item.UserId}");
+ sb.AppendLine();
+ sb.AppendLine("**Message:**");
+ sb.AppendLine($"```");
+ sb.AppendLine(item.Message);
+ sb.AppendLine($"```");
+ if (!string.IsNullOrEmpty(item.StackTrace))
+ {
+ sb.AppendLine();
+ sb.AppendLine("**Stack Trace:**");
+ sb.AppendLine("```");
+ sb.AppendLine(item.StackTrace);
+ sb.AppendLine("```");
+ }
+ sb.AppendLine();
+ }
+ }
+
+ // All logs in chronological order
+ sb.AppendLine("## Full Log (Chronological)");
+ sb.AppendLine();
+ sb.AppendLine("| Time (UTC) | Type | Title | Message |");
+ sb.AppendLine("|------------|------|-------|---------|");
+ foreach (var item in _logItems.OrderBy(i => i.Timestamp))
+ {
+ var msg = item.Message?.Replace("|", "\\|").Replace("\n", " ").Replace("\r", "") ?? "";
+ if (msg.Length > 100) msg = msg[..97] + "...";
+ var title = item.Title?.Replace("|", "\\|") ?? "";
+ if (title.Length > 50) title = title[..47] + "...";
+ sb.AppendLine($"| {item.Timestamp:HH:mm:ss.fff} | {item.TypeDisplay} | {title} | {msg} |");
+ }
+ sb.AppendLine();
+
+ // Detailed entries for non-exceptions
+ var otherItems = _logItems.Where(i => i.Type != "exception").OrderByDescending(i => i.Timestamp).ToList();
+ if (otherItems.Count > 0)
+ {
+ sb.AppendLine("## Other Log Details");
+ sb.AppendLine();
+ foreach (var item in otherItems)
+ {
+ sb.AppendLine($"### [{item.TypeDisplay}] {item.Title}");
+ sb.AppendLine($"**Time:** {item.Timestamp:yyyy-MM-dd HH:mm:ss.fff} UTC");
+ if (!string.IsNullOrEmpty(item.JourneyId))
+ sb.AppendLine($"**Journey ID:** {item.JourneyId}");
+ if (!string.IsNullOrEmpty(item.Message))
+ {
+ sb.AppendLine();
+ sb.AppendLine(item.Message);
+ }
+ sb.AppendLine();
+ }
+ }
+
+ return sb.ToString();
+ }
+
///
/// Get the current log items as a read-only list.
///
@@ -356,6 +497,33 @@ public partial class AppLogView : ContentPage
LogsShared?.Invoke(this, EventArgs.Empty);
}
+ private async void OnCopyForAIClicked(object? sender, EventArgs e)
+ {
+ if (_logItems.Count == 0)
+ {
+ await DisplayAlert("No Logs", "There are no logs to copy.", "OK");
+ return;
+ }
+
+ var content = ExportLogsForAI();
+ await Clipboard.Default.SetTextAsync(content);
+
+ // Show feedback
+ await MainThread.InvokeOnMainThreadAsync(async () =>
+ {
+ var originalText = CountLabel.Text;
+ CountLabel.Text = "Copied for AI!";
+ CountLabel.TextColor = Colors.Green;
+ await Task.Delay(1500);
+ CountLabel.Text = originalText;
+ CountLabel.TextColor = Application.Current?.RequestedTheme == AppTheme.Dark
+ ? Color.FromArgb("#9CA3AF")
+ : Color.FromArgb("#6B7280");
+ });
+
+ LogsCopiedForAI?.Invoke(this, EventArgs.Empty);
+ }
+
private async void OnLogSelected(object? sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is LogItem item)
diff --git a/IronServices.Maui.csproj b/IronServices.Maui.csproj
index da45e07..1e096cd 100644
--- a/IronServices.Maui.csproj
+++ b/IronServices.Maui.csproj
@@ -16,12 +16,18 @@
IronServices.Maui
IronServices.Maui
- Control library library for IronServices API (IronLicensing, IronTelemetry and IronNotify)
+ 1.1.0
+ MAUI UI controls for IronServices - includes LoginView, LicenseActivationView, AppLogView for telemetry logs, and UserJourneyView for visualizing user journeys with Timeline, Tree, and Flow view modes.
David H Friedel Jr
MarketAlly
IronServices.Maui
false
nuget_im.png
+ README.md
+ MIT
+ maui;licensing;telemetry;error-monitoring;user-journeys;ironservices;controls;ui
+ git
+ Copyright (c) 2025 MarketAlly
15.0
15.0
diff --git a/README.md b/README.md
index b147bd1..27d4eab 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,9 @@
MAUI controls and services for IronServices (IronLicensing, IronNotify, IronTelemetry).
+[](https://www.nuget.org/packages/IronServices.Maui/)
+[](https://opensource.org/licenses/MIT)
+
## Installation
```xml
@@ -37,6 +40,15 @@ builder.Services.AddIronServices(options =>
## Controls
+| Control | Type | Description |
+|---------|------|-------------|
+| [`LoginView`](#loginview) | ContentView | Drop-in login form |
+| [`LicenseActivationView`](#licenseactivationview) | ContentView | License key entry and activation |
+| [`AppLogView`](#applogview) | ContentPage | Exception and telemetry log viewer |
+| [`UserJourneyView`](#userjourneyview) | ContentPage | User journey visualization with Timeline/Tree/Flow modes |
+
+---
+
### LoginView
A drop-in login form for IronServices authentication.
@@ -154,6 +166,7 @@ A full-page log viewer for exceptions and telemetry events. Shows queued items f
| `Title` | `string` | `"App Logs"` | Page title (shown in navigation bar). |
| `ShowShareButton` | `bool` | `true` | Enable Share toolbar button. |
| `ShowClearButton` | `bool` | `true` | Enable Clear toolbar button. |
+| `EnableLiveUpdates` | `bool` | `false` | Auto-refresh when new items are added (polls every 2s). |
#### Events
@@ -182,12 +195,15 @@ A full-page log viewer for exceptions and telemetry events. Shows queued items f
| Property | Type | Description |
|----------|------|-------------|
| `Timestamp` | `DateTime` | UTC timestamp. |
-| `Type` | `string` | `"exception"`, `"message"`, `"info"`, `"warning"`, etc. |
+| `Type` | `string` | `"exception"`, `"message"`, `"journey_start"`, `"step_start"`, etc. |
| `Title` | `string` | Exception type name or message title. |
| `Message` | `string` | Log message. |
| `StackTrace` | `string?` | Stack trace for exceptions. |
| `JourneyId` | `string?` | Associated user journey ID. |
| `UserId` | `string?` | Associated user ID. |
+| `TypeDisplay` | `string` | Short type label: `"ERROR"`, `"MSG"`, `"START"`, etc. |
+| `TypeColor` | `Color` | Color for type badge. |
+| `TimestampDisplay` | `string` | Formatted local time (HH:mm:ss). |
#### Example
@@ -196,7 +212,8 @@ A full-page log viewer for exceptions and telemetry events. Shows queued items f
await Navigation.PushAsync(new AppLogView
{
TelemetryClient = _telemetryClient,
- Title = "Error Logs"
+ Title = "Error Logs",
+ EnableLiveUpdates = true
});
// With event handling
@@ -213,9 +230,176 @@ logView.AddLog("warning", "Network Issue", "Connection timeout after 30s");
#### Toolbar
+- **Refresh** (in header): Reloads from queue.
- **Share** (Primary): Exports logs as text and opens system share sheet.
-- **Clear** (Secondary/overflow menu): Clears displayed logs.
-- **Refresh** button in summary bar: Reloads from queue.
+- **Clear** (Secondary/overflow): Clears displayed logs.
+
+---
+
+### UserJourneyView
+
+A full-page user journey viewer with multiple visualization modes. Reconstructs journeys from `TelemetryClient` log items and displays steps, breadcrumbs, exceptions, and metadata.
+
+**Type:** `ContentPage` (standalone page, use with NavigationPage)
+
+#### View Modes
+
+| Mode | Description |
+|------|-------------|
+| `Timeline` | Vertical list with nested steps shown with indentation (default). |
+| `Tree` | Expandable tree hierarchy with collapsible journey/step nodes. |
+| `Flow` | Horizontal flowchart-style diagram showing step progression. |
+
+#### Properties
+
+| Property | Type | Default | Description |
+|----------|------|---------|-------------|
+| `TelemetryClient` | `TelemetryClient` | `null` | Client to pull journey data from. Auto-refreshes on set. |
+| `Title` | `string` | `"User Journeys"` | Page title. |
+| `ViewMode` | `JourneyViewMode` | `Timeline` | Current visualization mode. |
+| `EnableLiveUpdates` | `bool` | `false` | Auto-refresh when new items are added (polls every 2s). |
+| `ShowShareButton` | `bool` | `true` | Enable Share toolbar button. |
+| `ShowClearButton` | `bool` | `true` | Enable Clear toolbar button. |
+| `UserIdFilter` | `string?` | `null` | Filter to show only journeys for a specific user. |
+
+#### Events
+
+| Event | Args | Description |
+|-------|------|-------------|
+| `JourneySelected` | `JourneyItem` | User selected a journey from the list. |
+| `StepSelected` | `StepItem` | User selected a step in the detail panel. |
+| `JourneysRefreshed` | `EventArgs` | Journey list was refreshed. |
+| `JourneysCleared` | `EventArgs` | Journeys were cleared. |
+| `JourneysShared` | `EventArgs` | Journeys were exported/shared. |
+
+#### Methods
+
+| Method | Returns | Description |
+|--------|---------|-------------|
+| `RefreshJourneys()` | `void` | Reload journeys from TelemetryClient. |
+| `ClearJourneys()` | `void` | Clear displayed journeys. |
+| `ClearAllJourneys()` | `void` | Clear displayed journeys AND the TelemetryClient queue. |
+| `ExportJourneys()` | `string` | Export all journeys as formatted text. |
+| `GetJourneys()` | `IReadOnlyList` | Get current journey items. |
+| `JourneyCount` | `int` | Number of journeys. |
+
+#### JourneyItem Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `JourneyId` | `string` | Unique journey identifier. |
+| `Name` | `string` | Journey name (e.g., "Checkout Flow"). |
+| `UserId` | `string?` | Associated user ID. |
+| `UserEmail` | `string?` | Associated user email. |
+| `Status` | `JourneyDisplayStatus` | `InProgress`, `Completed`, `Failed`, `Abandoned`. |
+| `StartTime` | `DateTime` | When the journey started. |
+| `EndTime` | `DateTime?` | When the journey ended (null if in progress). |
+| `DurationMs` | `double?` | Duration in milliseconds. |
+| `DurationDisplay` | `string` | Formatted duration: `"125ms"`, `"2.3s"`, `"1.5m"`. |
+| `TimeAgo` | `string` | Relative time: `"just now"`, `"5m ago"`, `"2h ago"`. |
+| `StatusDisplay` | `string` | Human-readable status text. |
+| `StatusColor` | `Color` | Color for status badge (Blue/Green/Red/Gray). |
+| `StatusIcon` | `string` | Icon text: `"..."`, `"OK"`, `"X"`, `"-"`. |
+| `Steps` | `ObservableCollection` | Top-level steps in this journey. |
+| `Breadcrumbs` | `List` | Breadcrumbs captured during journey. |
+| `Exceptions` | `List` | Exceptions captured during journey. |
+| `Metadata` | `Dictionary` | Custom metadata. |
+| `StepCount` | `int` | Total steps (including nested). |
+| `FailedStepCount` | `int` | Number of failed steps. |
+| `HasFailedSteps` | `bool` | Whether any steps failed. |
+| `HasExceptions` | `bool` | Whether any exceptions occurred. |
+| `HasUser` | `bool` | Whether a user is associated. |
+
+#### StepItem Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `StepId` | `string` | Unique step identifier. |
+| `ParentStepId` | `string?` | Parent step ID for nested steps. |
+| `Name` | `string` | Step name (e.g., "Process Payment"). |
+| `Category` | `string?` | Category: `"business"`, `"technical"`, `"navigation"`. |
+| `Status` | `StepDisplayStatus` | `InProgress`, `Completed`, `Failed`, `Skipped`. |
+| `FailureReason` | `string?` | Reason for failure. |
+| `StartTime` | `DateTime` | When the step started. |
+| `EndTime` | `DateTime?` | When the step ended. |
+| `DurationMs` | `double?` | Duration in milliseconds. |
+| `DurationDisplay` | `string` | Formatted duration. |
+| `StatusColor` | `Color` | Color for status indicator. |
+| `StatusIcon` | `string` | Icon text. |
+| `ChildSteps` | `ObservableCollection` | Nested child steps. |
+| `NestingLevel` | `int` | Depth level for indentation. |
+| `IndentMargin` | `Thickness` | Calculated margin for indentation. |
+
+#### BreadcrumbItem Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `Timestamp` | `DateTime` | When captured. |
+| `Category` | `string` | Breadcrumb category. |
+| `Message` | `string` | Breadcrumb message. |
+| `Level` | `string` | Log level: `"info"`, `"warning"`, `"error"`, `"debug"`. |
+| `LevelColor` | `Color` | Color based on level. |
+| `Data` | `Dictionary?` | Additional data. |
+
+#### ExceptionItem Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `Timestamp` | `DateTime` | When captured. |
+| `ExceptionType` | `string` | Exception type name. |
+| `Message` | `string` | Exception message. |
+| `StackTrace` | `string?` | Stack trace. |
+| `StepId` | `string?` | Step ID where exception occurred. |
+| `HasStackTrace` | `bool` | Whether stack trace is available. |
+
+#### Example
+
+```csharp
+// Basic usage
+await Navigation.PushAsync(new UserJourneyView
+{
+ TelemetryClient = _telemetryClient,
+ EnableLiveUpdates = true
+});
+
+// With view mode and filtering
+var journeyView = new UserJourneyView
+{
+ TelemetryClient = _telemetryClient,
+ ViewMode = JourneyViewMode.Tree,
+ UserIdFilter = currentUser.Id
+};
+await Navigation.PushAsync(journeyView);
+
+// Event handling
+var journeyView = new UserJourneyView { TelemetryClient = _telemetryClient };
+journeyView.JourneySelected += (s, journey) =>
+{
+ Debug.WriteLine($"Selected: {journey.Name} ({journey.StepCount} steps)");
+};
+journeyView.StepSelected += (s, step) =>
+{
+ Debug.WriteLine($"Step: {step.Name} - {step.StatusDisplay}");
+};
+await Navigation.PushAsync(journeyView);
+```
+
+#### Detail Panel
+
+When a journey is selected, a detail panel shows:
+- **Status, Duration, User** - Summary info
+- **Steps** - List of steps with status indicators and timing
+- **Breadcrumbs** - Timestamped breadcrumb trail
+- **Exceptions** - Exception details (tap to copy)
+- **Metadata** - Key-value pairs
+- **Copy to Clipboard** - Export journey details
+
+#### Toolbar
+
+- **Timeline/Tree/Flow** buttons - Switch view modes
+- **Share** (Primary) - Export journeys as text
+- **Clear** (Secondary) - Clear displayed journeys
+- **Refresh** - Reload from TelemetryClient
---
@@ -310,7 +494,20 @@ private async void OnViewLogsClicked(object sender, EventArgs e)
var telemetry = Handler.MauiContext.Services.GetService();
await Navigation.PushAsync(new AppLogView
{
- TelemetryClient = telemetry
+ TelemetryClient = telemetry,
+ EnableLiveUpdates = true
+ });
+}
+
+// SettingsPage.xaml.cs - Navigate to journeys
+private async void OnViewJourneysClicked(object sender, EventArgs e)
+{
+ var telemetry = Handler.MauiContext.Services.GetService();
+ await Navigation.PushAsync(new UserJourneyView
+ {
+ TelemetryClient = telemetry,
+ EnableLiveUpdates = true,
+ ViewMode = JourneyViewMode.Timeline
});
}
```
@@ -329,6 +526,8 @@ All controls use MAUI resource dictionaries. Override these colors in your `App.
#919191
#6E6E6E
#404040
+#374151
+#1F2937
#212121
#FFFFFF
```
@@ -342,8 +541,8 @@ Controls support light/dark themes via `AppThemeBinding`.
| Platform | Minimum Version |
|----------|-----------------|
| Android | API 21 (5.0) |
-| iOS | 14.0 |
-| macOS | 11.0 |
+| iOS | 15.0 |
+| macOS | 15.0 |
| Windows | 10.0.17763.0 |
---
@@ -352,11 +551,11 @@ Controls support light/dark themes via `AppThemeBinding`.
- `IronServices.Client` - Core API client
- `IronLicensing.Client` - License validation
-- `IronTelemetry.Client` - Telemetry and logging
+- `IronTelemetry.Client` - Telemetry, journeys, and logging
- `Microsoft.Maui.Controls` - MAUI framework
---
## License
-Proprietary. See LICENSE file.
+MIT License - see [LICENSE](LICENSE) file.