Technical Debt

Known issues in the codebase, ordered by severity. This page is updated as items are resolved or discovered.

Last verified against code: June 2026. Line counts and locations below reflect the current source; several items grew since first documented.


πŸ”΄ High

1. XboxDeviceService β€” God class

File: XBVault/Services/XboxDeviceService.cs Β· 1,207 lines Β· 35 public methods/properties

Problem: Mixes HTTP calls, WebSocket handling, JSON parsing, and logging across 8 unrelated domains β€” packages, processes, crash dumps, network, system info, screenshots, performance, restart/shutdown. Every new feature adds another method to the same class, making it hard to test, navigate, or refactor.

Fix: Split into:

Proposed class Responsibility
XboxPackageService install, uninstall, launch, suspend, terminate, list
XboxProcessService list, kill, running title
XboxCrashService list crash dumps, delete, crash control
XboxNetworkService network config, wifi interfaces/networks
XboxSystemService system info, restart, shutdown, screenshot
XboxPerformanceService WebSocket connection, snapshot parsing

2. _Backup/ directory tracked in git βœ… Resolved (v0.8.x)

Removed from tracking, added to .gitignore, deleted from disk.


🟑 Medium

3. App.axaml.cs β€” 497 lines, manual composition root

File: App.axaml.cs

The main startup file has grown to 497 lines and does too much:

  • Manually instantiates the core services with new (XboxDeviceService, CacheService, PackageInstallService, SftpService) β€” no DI container
  • InitAfterSplashAsync is a 384-line method wiring all 22 window delegate callbacks, sidebar views, catalog load, splash close, and first-run wizard
  • Contains 2 bare catch { } blocks at lines 107 and 110 inside ShowErrorDialogSafe β€” errors in the error dialog are silently swallowed

Fix: Extract dialog wiring into a DialogRegistry class. Consider a lightweight DI container (Microsoft.Extensions.DependencyInjection) to replace manual new.


4. No ConfigureAwait(false) anywhere

~82–100 await calls across Services/. Zero have .ConfigureAwait(false).

Spread across XboxDeviceService.cs (~50+), PackageInstallService.cs, CatalogApiService.cs, and SftpService.cs.

Service-layer continuations unnecessarily capture the UI synchronization context, which can cause deadlocks and reduces throughput.

Fix: Add .ConfigureAwait(false) to all await calls in Services (HTTP, file I/O, WebSocket). Skip in ViewModels that update ObservableProperty on the UI thread.


5. Silent exception swallowing β€” multiple sites βœ… Resolved (v0.9.2)

Several catch blocks discard exceptions with no logging, no rethrow, and no meaningful fallback:

File Pattern Fix
App.axaml.cs catch { } Logger.Error added
Services/XboxDeviceService.cs:425 catch { } Logger.Warn added
Services/XboxDeviceService.cs:624 catch { /* Ignore */ } Logger.Trace added
Services/XboxDeviceService.cs:1181 catch { } Logger.Trace added
ViewModels/NetworkInfoViewModel.cs:50 catch { } Logger.Warn added
ViewModels/CrashDataViewModel.cs:76 catch { CrashDumpEnabled = false; } Logger.Warn added
ViewModels/ConnectionViewModel.cs:82 catch { } Logger.Warn added
Services/AdminHelper.cs catch { return false; } File deleted (dead code)
Services/CryptoService.cs:41 catch { return string.Empty; } Logger.Warn added
Services/Logger.cs catch { } Γ— 3 Left as-is (logger can’t log itself)

Fix: Added Logger.Warn/Error/Trace to all non-intentional silent catches. Deleted dead AdminHelper.cs.


6. async void in code-behind (fire-and-forget)

Verification found 11 instances (the original docs listed 4) β€” unhandled exceptions in async void crash the process with no recovery:

File Line Method Risk
Views/ConnectionWindow.axaml.cs 37 OnConnectionCompleted High
Views/ErrorDialog.axaml.cs 60 OnCopyClick High
Views/FileExplorerView.axaml.cs 272 OnTreeItemExpanded High
Views/FileExplorerView.axaml.cs 284 OnBrowseFilesClick High
Views/FileExplorerView.axaml.cs 562 OnDropZoneDrop High
Views/NetworkInfoWindow.axaml.cs 15 OnLoaded High
Views/LogsView.axaml.cs 41 OnCopyClick Low
Views/SftpInfoWindow.axaml.cs 23 OnCopyHostClick Low
Views/SftpInfoWindow.axaml.cs 35 OnCopyPortClick Low
Views/SftpInfoWindow.axaml.cs 47 OnCopyUserClick Low
Views/SftpInfoWindow.axaml.cs 59 OnCopyPasswordClick Low

The high-risk handlers perform real async work (connection, SFTP tree expansion, file browse, drag-drop upload, network load); the low-risk ones are clipboard copies. The FileExplorerView handlers arrived with the v0.8.7 file explorer.

Fix: Wrap body in a safe FireAndForget extension with exception logging, or restructure to async Task where possible.


7. XboxDeviceService does not implement IDisposable

File: XBVault/Services/XboxDeviceService.cs:16

The class holds HttpClient _http and HttpClientHandler? _handler (both disposable) but has no Dispose() method and does not implement IDisposable. The callers in App.axaml.cs cannot dispose it at shutdown.

Internal Configure() re-creates and disposes old instances on reconfiguration, but this is fragile and there’s no cleanup path on normal app exit.

Fix: Implement IDisposable (or IAsyncDisposable) and dispose _http and _handler properly.


8. Border CornerRadius does not clip Image (Avalonia 12.0.0)

Files: Views/BrowseView.axaml, Views/ItemDetailWindow.axaml

Border CornerRadius="8,8,0,0" with Image Stretch="UniformToFill" inside does not clip to rounded corners β€” image corners bleed through.

Tried: overlay Border stroke, separate Border with CornerRadius, ClipToBounds="True". None worked.

Next steps:

  • Apply Clip geometry via code-behind (RectangleGeometry with RadiusX/Y bound to ActualWidth/ActualHeight)
  • Or use ImageBrush inside a Border (different render path, may clip correctly)
  • Check if a newer Avalonia patch resolves this

9. Title bar gradient duplicated across windows βœ… Resolved (v0.9.1)

The same LinearGradientBrush (#447F3E β†’ #9ACA3C) is defined inline in every dialog window plus MainWindow β€” some windows define it twice (title bar + table header). SetupWizardWindow and UsbPermissionWindow added in v0.8.6 continue the pattern.

Fix: Extract as a named StaticResource in BladesTheme.axaml:

<LinearGradientBrush x:Key="TitleGradient" StartPoint="0%,0%" EndPoint="100%,0%">
  <GradientStop Color="#447F3E" Offset="0"/>
  <GradientStop Color="#9ACA3C" Offset="1"/>
</LinearGradientBrush>

10. Close button template duplicated across windows βœ… Resolved (v0.9.1)

Same <Button> with inline styles (#CC3333 hover, 32Γ—32, transparent default) copy-pasted into every window, including new windows added in v0.8.6.

Fix: Create a reusable WindowCloseButton style or UserControl in BladesTheme.axaml.


🟒 Low

11. Hardcoded magic delays β€” 23 instances βœ… Resolved (v0.9.1)

Task.Delay(ms) with hardcoded numbers across Views, ViewModels, and Services:

File Lines Values
Views/ConnectionWindow.axaml.cs 41, 43 2000, 1500
Views/RefreshWindow.axaml.cs 51 1500
Views/LogsView.axaml.cs 58 2000
App.axaml.cs 125 2000 (splash delay)
ViewModels/ConnectionViewModel.cs 105–172 300, 500, 400, 600, 300, 250, 250, 350, 350, 350, 350 (animation timing)
ViewModels/BrowseViewModel.cs 564 3000
ViewModels/UsbPermissionViewModel.cs 227 1000
ViewModels/RefreshViewModel.cs 69 200
ViewModels/SettingsViewModel.cs 77 3000
Services/XboxDeviceService.cs 572, 587 2000, 3000

Fix: Name as const int or static readonly TimeSpan with a descriptive identifier.


12. CatalogApiService not injected β€” created inline in two places βœ… Resolved (v0.9.2)

Files: ViewModels/BrowseViewModel.cs:40, Services/CatalogApiService.cs:309

BrowseViewModel creates its own CatalogApiService instance in the constructor (_catalogService = new CatalogApiService()), bypassing the composition root in App.axaml.cs. Additionally, CatalogApiService.cs:309 creates an instance of itself inside a static method (LoadFromCache) to call ClassifyDownloads β€” a class instantiating itself as a utility.

This means there are potentially 3 separate CatalogApiService instances alive at runtime, but only 1 registered cache/service in App.axaml.cs.

Fix: Inject CatalogApiService via constructor in BrowseViewModel. Extract ClassifyDownloads to a static method to avoid self-instantiation.


13. PerformanceViewModel β€” CancellationTokenSource never disposed

File: ViewModels/PerformanceViewModel.cs

_cts?.Cancel() is called before reassignment, but _cts.Dispose() is never called and the class does not implement IDisposable. CancellationTokenSource holds a WaitHandle that is not released until GC finalizer.

Fix: Call _cts?.Dispose() before nulling, implement IDisposable.


14. DllImport in Logger + System.Management load-time risk on Linux βœ… Resolved (v0.9.2)

Two Windows-specific dependencies:

File Issue
Services/Logger.cs [DllImport("kernel32.dll")] β€” call site is guarded with OperatingSystem.IsWindows(), but P/Invoke metadata is always emitted
Services/UsbDriveDetector.cs using System.Management β€” runtime guard exists at line 13, but the assembly reference is load-time; if System.Management is absent from the Linux publish output, the app may fail to start

The Linux and macOS release artifacts in CI publish with --self-contained true, which bundles all referenced assemblies. Verify that System.Management (a Windows-only NuGet) is excluded or stubbed in the Linux/macOS publish output.

Fix Logger: Already functionally guarded. The DllImport declaration itself is low risk β€” no further action required unless P/Invoke metadata size is a concern.

Fix UsbDriveDetector: using System.Management and all WMI code wrapped in #if WINDOWS_BUILD (csproj defines WINDOWS_BUILD only on Windows builds). Non-Windows builds get a no-op fallback, no assembly reference needed.


15. PerformanceSnapshot.cs β€” catch with no log βœ… Resolved

The previously documented silent catch at PerformanceSnapshot.cs:78 now calls Logger.Error(ex, "Failed to parse PerformanceSnapshot"). Item closed.


16. BrowseViewModel.cs β€” 580 lines

Approaching the god-class threshold (grew from ~499 to 580 lines). Contains catalog loading, filtering, search, item selection, install orchestration, progress reporting, and image thumbnail management.

Fix: No immediate action required, but monitor. Consider extracting install-related logic into a dedicated coordinator if it grows further.


17. Orphaned _Backup icons βœ… Resolved (v0.9.1)

Deleted Assets/_Backup/ directory β€” all files were already untracked (gitignored) and unreferenced.


18. File Explorer drive list is hardcoded β€” no discovery

File: ViewModels/FileExplorerViewModel.cs:429 (DetectDrivesAsync)

The File Explorer surfaces a static set of drives β€” { "C", "D", "E", "G", "J", "L", "M", "N", "Q", "S", "T", "U", "V", "X", "Y" } β€” with no runtime discovery. The list was expanded in v0.9.1 to match WinSCP’s default set, which covers current Xbox Dev Mode drive layouts well.

The Xbox Dev Mode drive layout is not guaranteed stable β€” the external-storage letter has changed historically (it was not always E:), and consoles may expose additional or differently-lettered volumes. With a fixed list, real drives can be missing (not shown) or dead entries can appear (shown but unmountable).

Fix: Discover drives dynamically over SSH instead of hardcoding β€” e.g. wmic logicaldisk get name, or probe cd {letter}: && echo ok across the alphabet, and build the list from what actually responds. Keep the current set only as a fallback if discovery fails.

Status: Sufficiently addressed for now β€” the expanded list covers all known Xbox drive letters.


Summary

graph LR
    H["πŸ”΄ High<br/>1 open Β· 1 resolved"]
    M["🟑 Medium<br/>5 open · 3 resolved"]
    L["🟒 Low<br/>2 open · 5 resolved"]
    
    style H fill:#CC3333,stroke:#9ACA3C,color:#fff
    style M fill:#FF9900,stroke:#9ACA3C,color:#000
    style L fill:#9ACA3C,stroke:#447F3E,color:#000
Severity Open Resolved Estimated effort
πŸ”΄ High 1 1 βœ… 4–6 hours
🟑 Medium 5 3 βœ… 6–12 hours
🟒 Low 2 5 βœ… 0–2 hours
Total 8 open 9 resolved 10–20 hours

Notable changes since first documented

  • async void 4 β†’ 11 instances (the v0.8.7 File Explorer added 3 high-risk handlers).
  • XboxDeviceService 1,038 β†’ 1,207 lines; App.axaml.cs 455 β†’ 497 (InitAfterSplashAsync 342 β†’ 384); BrowseViewModel 499 β†’ 580.
  • Silent catches: 12–14+ confirmed sites (more than the 8 first listed).
  • ConfigureAwait(false): still 0 across the service layer.
  • Resolved #9, #10, #11: TitleGradient, unified close button, magic delays (v0.9.1).
  • Resolved #12: CatalogApiService injected via constructor (v0.9.2).
  • Resolved #14: UsbDriveDetector WMI code conditionally compiled for Windows only (v0.9.2).

← Roadmap Β· ← Home