Skip to content

Develop#15

Merged
ucswift merged 4 commits into
masterfrom
develop
Jun 25, 2026
Merged

Develop#15
ucswift merged 4 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

This PR introduces a complete WPF desktop GUI application for Resgrid Relay, replacing the console-only experience with a modern Windows application, and includes several engine reliability and correctness fixes.

Desktop GUI Application (Resgrid.Audio.Relay)

  • Full WPF-UI/Fluent shell with left navigation rail hosting six screens: Dashboard, Operations, Configuration, Logs, Devices, and About
  • System tray integration — closing the window hides to tray while relay modes keep running; a real quit happens via the tray menu
  • Single-instance enforcement via a named mutex, with a message box if another instance is already running
  • Dependency injection container wiring all views, view-models, and services
  • Dashboard with live service tiles showing connection health pills, audio level meters, squelch/TX/RX indicators, and traffic counters
  • Operations screen with per-mode Start/Stop cards, pre-flight validation summaries, and lifecycle state indicators
  • Configuration screen providing a full editor over all relay settings with secret masking/reveal toggles, validation, and atomic config file saves (also surfaces environment-override warnings)
  • Logs screen with a live streaming log view (~10 Hz batched), minimum-level filtering, auto-scroll, copy, and clear
  • Devices screen enumerating audio input/output devices, serial ports, and CM108-class HID devices
  • About screen with version info and a run-at-Windows-startup toggle (HKCU Run key)
  • Custom controls (LevelMeter, Sparkline, StatusPill) and a suite of value converters for consistent status visualization

Engine Improvements (Resgrid.Relay.Engine)

  • Service lifecycle hardening: re-entrant start protection, atomic state/CTS transitions so StopAsync can't slip through a startup window, and pre-flight failure detection that transitions services to Faulted instead of silently reporting success
  • Accurate connection-state reporting: API and TTS connections now report Connected only after actual verification rather than optimistically at startup; Redis and TTS use Unknown (unprobed) instead of a stuck Connecting state
  • RadioBridge squelch/receiving fix: prevents own TX feedback (during AntiLoop suppression) from being incorrectly counted as receiving audio, which would have suppressed in-progress transmits
  • Configuration layering system: layered config (appsettings.json → per-user relay.user.json → RESGRID__RELAY__ env vars) with atomic writes and env-override detection
  • UI log pipeline: a bounded, lossy channel-based log bus and Serilog sink that feeds log records to the desktop UI
  • Mode factory now defaults to smtp when mode is null or whitespace

Build Configuration

  • Added Directory.Build.props defining the NET10_0_WINDOWS preprocessor symbol for the net10.0-windows TFM, ensuring Windows-only code (radio/audio modes, device enumeration, WPF) compiles into the Windows build while staying excluded from cross-platform/Linux builds

Summary by CodeRabbit

  • New Features

    • Introduced a new tray-based relay shell with navigation, start/stop controls, and single-instance startup.
    • Added new Dashboard, Operations, Configuration, Logs, Devices, and About screens.
    • Added visual status indicators, audio level meters, sparklines, and improved log viewing.
  • Bug Fixes

    • Improved relay state reporting and prevented misleading “receiving” status during feedback loops.
    • Better handling of service startup/shutdown and connection states for more accurate UI feedback.
  • Refactor

    • Updated app startup and configuration handling for a cleaner, more reliable desktop experience.

@Resgrid-Bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ucswift, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 124d6c8f-cfee-43e9-a361-a55102ebc07c

📥 Commits

Reviewing files that changed from the base of the PR and between e16ca16 and ac4162a.

📒 Files selected for processing (1)
  • Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs
📝 Walkthrough

Walkthrough

The PR adds a DI-backed WPF relay shell, layered configuration and logging infrastructure, updated relay service lifecycle and radio state handling, shared UI controls/converters, and new About, Configuration, Devices, Logs, and Operations pages.

Changes

Desktop shell and relay flow

Layer / File(s) Summary
Bootstrap and logging plumbing
Directory.Build.props, Resgrid.Audio.Core/AudioEvaluator.cs, Resgrid.Audio.Core/WatcherAudioStorage.cs, Resgrid.Audio.Relay/App.*, Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj, Resgrid.Audio.Relay/Services/ConfigurationService.cs, Resgrid.Relay.Engine/Configuration/RelayConfiguration.cs, Resgrid.Relay.Engine/Logging/*
AudioEvaluator and WatcherAudioStorage switch to Serilog ILogger; RelayConfiguration, UiLogBus, UiLogSink, and ConfigurationService add layered config and UI log transport; and App builds the DI container, applies the shell theme, and cleans up on exit.
Service lifecycle
Resgrid.Relay.Engine/Services/*.cs, Resgrid.Relay.Engine/Voice/AudioImportMode.cs, Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs, Resgrid.Relay.Engine/Services/SmtpRelayService.cs
RelayServiceBase adds ThrowIfFailed and tighter start/stop coordination; the service wrappers now use it; and audio, dispatch, and SMTP flows update connection-state reporting and mode selection defaults.
Radio bridge state
Resgrid.Audio.Core/Radio/RadioBridge.cs, Resgrid.Relay.Engine/Voice/RadioMode.cs
RadioBridge now suppresses Receiving during closed squelch and anti-loop transmit, and RadioMode subscribes to TX/RX updates before starting the bridge.
Shared controls and converters
Resgrid.Audio.Relay/Controls/*, Resgrid.Audio.Relay/Converters/*
New WPF controls and shared converters add level meters, sparkline rendering, status pills, shared brushes, and binding helpers for booleans, connection and relay states, log levels, secrets, squelch text, counts, and tune button text.
Shell and dashboard
Resgrid.Audio.Relay/Services/RelayController.cs, Resgrid.Audio.Relay/ShellWindow.*, Resgrid.Audio.Relay/ViewModels/ShellViewModel.cs, Resgrid.Audio.Relay/ViewModels/DashboardViewModel.cs, Resgrid.Audio.Relay/ViewModels/ServiceTileViewModel.cs, Resgrid.Audio.Relay/Views/DashboardView.*
RelayController tracks running services and overall state, ShellViewModel exposes master start/stop state, DashboardViewModel and ServiceTileViewModel mirror per-service tiles, and the shell window hosts tray navigation and dashboard content.
About screen
Resgrid.Audio.Relay/ViewModels/AboutViewModel.cs, Resgrid.Audio.Relay/Views/AboutView.*
AboutViewModel exposes version text, startup registry settings, and external links, and the About view binds those values and commands.
Configuration editor
Resgrid.Audio.Relay/ViewModels/ConfigurationViewModel.cs, Resgrid.Audio.Relay/Views/ConfigurationView.*
ConfigurationViewModel flattens relay options into editable fields, masks and preserves secrets, validates saves, runs the tune meter, and the configuration view binds the cards and status actions.
Devices and logs pages
Resgrid.Audio.Relay/Services/DeviceEnumerationService.cs, Resgrid.Audio.Relay/ViewModels/DevicesViewModel.cs, Resgrid.Audio.Relay/Views/DevicesView.*, Resgrid.Audio.Relay/ViewModels/LogsViewModel.cs, Resgrid.Audio.Relay/Views/LogsView.*
Device enumeration returns audio, serial, and CM108 device lists for the devices page, and LogsViewModel streams UiLogBus records into a filtered, copyable log list with auto-scroll support.
Operations page
Resgrid.Audio.Relay/ViewModels/ModeCardViewModel.cs, Resgrid.Audio.Relay/ViewModels/OperationsViewModel.cs, Resgrid.Audio.Relay/Views/OperationsView.*
ModeCardViewModel and OperationsViewModel build per-mode cards with validation, start/stop commands, and cloned options, and the operations view renders those cards with state, validation, and control bindings.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Resgrid/Relay#7: The main PR updates Resgrid.Audio.Core/ComService to call the new Resgrid v4 API client, while the retrieved PR adds those v4 API client entry points and models at the same call surface.
  • Resgrid/Relay#12: This PR adds NET10_0_WINDOWS for net10.0-windows builds, which directly supports #if NET10_0_WINDOWS code in the relay engine.
  • Resgrid/Relay#13: Both PRs modify Resgrid.Audio.Core/Radio/RadioBridge.cs receive-state handling around squelch and anti-loop forwarding.

Poem

I twitch my nose at logs at night,
And nibble themes in tray-lit glow;
The relay hums, the meters light,
While config cards and bouncy views all grow.
Thump-thump—my paws applaud the flow.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague and generic to describe the changeset or its main purpose. Use a concise, specific title that summarizes the main change, such as adding the WPF desktop GUI and related relay engine improvements.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Resgrid.Audio.Core/WatcherAudioStorage.cs (1)

19-26: 🩺 Stability & Availability | 🟡 Minor

Static _watcherAudio is safe under current deployment model.

WatcherAudioStorage is instantiated directly via new WatcherAudioStorage(logger) in Resgrid.Audio.Relay.Console/Program.cs, not registered in the DI container. The application runs as a single-process relay creating exactly one instance per lifecycle, preventing the static dictionary reset conflict described.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Core/WatcherAudioStorage.cs` around lines 19 - 26, The concern
is the static `_watcherAudio` field in `WatcherAudioStorage`, but this class is
created once directly from `Program` and not through DI, so the current
singleton-like lifecycle is safe. Keep `_watcherAudio` as static and initialized
in the `WatcherAudioStorage(ILogger logger)` constructor; no scoped-instance
refactor is needed. If you want to make the assumption clearer, add a brief note
near the field or constructor referencing the single-instance relay lifecycle
used by `Program`.
🧹 Nitpick comments (3)
Resgrid.Audio.Relay/ShellWindow.xaml.cs (1)

143-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent VM-less fallback in GetPage masks DI misconfiguration.

When a page type isn't registered, _provider.GetService(pageType) returns null and the code falls back to Activator.CreateInstance(pageType). For these views the parameterless constructor runs InitializeComponent() but never sets DataContext/_viewModel, so the user silently gets a blank, non-functional page instead of a clear failure. Consider failing fast (or logging) so a missing registration surfaces during development.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/ShellWindow.xaml.cs` around lines 143 - 146, The GetPage
fallback is silently creating pages without DI when a type is not registered,
which hides configuration errors and can leave views without a DataContext or
_viewModel; update GetPage in ShellWindow.xaml.cs to fail fast or explicitly log
when _provider.GetService(pageType) returns null instead of always falling back
to Activator.CreateInstance(pageType), so missing registrations surface
immediately during development.
Resgrid.Audio.Relay/Views/DevicesView.xaml.cs (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale doc comment. The XML summary still calls this a "Placeholder Devices screen — filled in by the next UI pass," but the view and its bindings are now implemented. Update the comment to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/Views/DevicesView.xaml.cs` at line 6, The XML summary on
DevicesView is stale and still describes the view as a placeholder even though
the bindings and UI are implemented. Update the summary comment in
DevicesView.xaml.cs to reflect the actual purpose of the DevicesView class,
keeping it accurate and concise so it matches the current implementation.
Resgrid.Audio.Relay/Converters/BoolToVisibilityConverter.cs (1)

15-17: 📐 Maintainability & Code Quality | 🔵 Trivial

Immutable shared singleton instance.

Resgrid.Audio.Relay.csproj targets net10.0-windows, which fully supports init accessors. Because Instance is a shared static resource, the Invert property should be set once and never mutated across the app. Change the accessor to get; init; to prevent unintended state changes if a consumer reuses the shared instance.

Refactor
-	public bool Invert { get; set; }
+	public bool Invert { get; init; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/Converters/BoolToVisibilityConverter.cs` around lines 15
- 17, The shared singleton in BoolToVisibilityConverter should be immutable so
its state cannot be changed after initialization. Update the Invert property on
BoolToVisibilityConverter to use an init-only accessor instead of a set
accessor, keeping Instance as the shared static entry point and preventing later
mutation from consumers that reuse it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Resgrid.Audio.Relay/App.xaml.cs`:
- Around line 82-89: The logging setup in App.xaml.cs is inconsistent with its
comment because the LoggerConfiguration in the logger initialization only uses
WriteTo.UiBus(logBus) and does not include a Debug sink. Update the
implementation to match the intended behavior by either adding the missing
WriteTo.Debug() sink in the LoggerConfiguration chain or removing the “plus
Debug for local diagnostics” wording from the comment so it accurately reflects
the actual sinks configured.

In `@Resgrid.Audio.Relay/Controls/LevelMeter.xaml.cs`:
- Around line 37-42: Update LevelMeter.xaml.cs so UpdateVisual uses the same
clamped value for both the bar and the label. Right now Bar.Value is based on
the clamped percentage, but Label.Text still formats the raw db input; change
the label to display the clamped dBFS value derived in UpdateVisual (or a shared
clamped variable) so the UI stays consistent with the -80..0 range comment.

In `@Resgrid.Audio.Relay/Controls/Sparkline.cs`:
- Around line 90-107: The Sparkline control only detaches from the previous
Values collection in OnValuesChanged, so it can stay subscribed after the view
is unloaded and leak via OnValuesCollectionChanged. Update Sparkline to
unsubscribe from the current INotifyCollectionChanged when the control is
unloaded/disposed, and re-subscribe when it is loaded again if needed. Use the
existing OnValuesChanged and OnValuesCollectionChanged hooks to centralize the
attach/detach logic so the control stops receiving CollectionChanged events once
it is no longer on screen.

In `@Resgrid.Audio.Relay/Controls/StatusPill.xaml`:
- Around line 13-21: The StatusPill control currently renders only the static
Label via the TextBlock in StatusPill.xaml, so the visual state is communicated
only by the background brush. Update the StatusPill template and its Root
bindings so the displayed text includes the actual State value (or a
state-derived label) alongside the existing Label, keeping DashboardView callers
like API and LiveKit unchanged. Make the text content in the StatusPill control
reflect both the label and connection state so screen readers and non-color
users can perceive status without relying on the brush alone.

In `@Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj`:
- Line 24: The NAudio dependency version in
Resgrid.Audio.Relay/Resgrid.Audio.Core is out of sync with the checked-in
DtmfDetection.NAudio binary. Verify DtmfDetection.NAudio.dll is compatible with
NAudio 1.10.0; if not, rebuild or replace the binary so it matches the current
NAudio package version. Update the references in Resgrid.Audio.Core and any
related project settings so the DtmfDetection path and NAudio PackageReference
stay aligned.

In `@Resgrid.Audio.Relay/Services/RelayController.cs`:
- Around line 97-118: The race is in RelayController when the entry is added to
_entries before Entry.RunTask is initialized, allowing StopAsync to see a
partially published entry. Update the Start/launch path so the background task
is created first and the entry is fully initialized before publication, or make
the _entries insert and RunTask assignment happen atomically under _gate; use
RelayController, Entry.RunTask, and the StartAsync/task-launch block to locate
the fix.
- Around line 30-35: `RelayController` is still using
`SynchronizationContext.Current` and can end up posting `RunningServices`
updates on the caller thread when the context is null. Update `RelayController`
to marshal through `Application.Current.Dispatcher` instead of `_uiContext`, and
adjust the `Post`/collection-update path so every UI-bound change is invoked on
the UI thread. Keep the change centered around `RelayController` and the
`ServiceTileViewModel`-triggered update flow so the dispatcher is used
consistently.

In `@Resgrid.Audio.Relay/ViewModels/ConfigurationViewModel.cs`:
- Around line 512-514: The tuning path in ConfigurationViewModel is mutating the
shared live configuration instead of a temporary snapshot. Update the code
around ApplyToOptions so it operates on an isolated copy of the current
settings, not _configuration.Current, and only uses the edited Radio values
needed for tuning. Make sure the tuner gets a cloned or disk-loaded options
instance while leaving the shared Current object untouched so uncommitted form
changes do not affect active services like RelayController.

In `@Resgrid.Audio.Relay/ViewModels/ModeCardViewModel.cs`:
- Around line 61-76: Update ModeCardViewModel so the Start button is disabled
when pre-flight validation has already failed. The current CanStart() only
checks IsRunning, so include HasValidationErrors in that predicate (alongside
!IsRunning) and keep notifying the StartCommand state after validation changes.
Use the ModeCardViewModel.CanStart method and the HasValidationErrors flag to
locate the fix, and ensure the UI state matches OperationsViewModel.StartMode
behavior.

In `@Resgrid.Audio.Relay/ViewModels/OperationsViewModel.cs`:
- Around line 64-95: StartMode currently relies on IsRunning/FindRunning, which
only read RelayController.RunningServices and miss the async gap before
RunningServices.Add is posted from Start. Update OperationsViewModel so the
duplicate-start check uses authoritative controller state from RelayController
(or add a local pending-start guard in StartMode) instead of the delayed UI
collection, and keep the validation short-circuit behavior intact.

In `@Resgrid.Audio.Relay/Views/AboutView.xaml.cs`:
- Line 6: The summary on AboutView is stale and still describes the view as a
placeholder even though the page is implemented. Update or remove the XML doc
comment on AboutView in AboutView.xaml.cs so it accurately reflects the current
behavior and features exposed by the view, keeping the description aligned with
the actual UI instead of the old placeholder wording.

In `@Resgrid.Audio.Relay/Views/LogsView.xaml.cs`:
- Around line 39-49: The `OnUnloaded` cleanup in `LogsView` is disposing and
nulling `_viewModel`, which breaks the page when WPF `Frame` back-navigation
reuses the same view instance. Update `OnUnloaded` to only detach
`EntriesAppended` and avoid calling `Dispose()` or clearing `_viewModel` unless
the view is truly being destroyed; if reuse is expected,
reinitialize/re-subscribe in `OnLoaded`/`OnNavigatedTo` so the pump and handler
are restored when the page comes back.

In `@Resgrid.Relay.Engine/Services/RelayServiceBase.cs`:
- Around line 77-81: The startup flow in RelayServiceBase.StartAsync can
overwrite a concurrent StopAsync state change by transitioning to Running
unconditionally before ExecuteAsync begins. Update the Running transition in
StartAsync to only occur when the service is still in Starting, or when the
startup token has not already been canceled by StopAsync, so a stop that wins
the startup race keeps the service in Stopping/Stopped instead of being reverted
to Running. Use the existing TransitionTo, _state, _cts, and ExecuteAsync
symbols to place the guard at the point where the running state is published.

In `@Resgrid.Relay.Engine/Services/RelayServiceFactory.cs`:
- Line 20: The RelayServiceFactory mode selection is silently defaulting
whitespace-only values to smtp, which should be treated as a configuration error
instead. Update the mode resolution in RelayServiceFactory so only a null
options.Mode falls back to the default, while trimmed-empty or whitespace-only
values are rejected explicitly before any relay is created; use the existing
mode-handling logic in RelayServiceFactory to keep the validation centralized.

In `@Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs`:
- Around line 57-61: The TTS state in DispatchVoiceMode is left at Unknown after
repeated announcement failures, so the per-call failure path never reflects that
the service is unreachable. Update the AnnounceAsync exception handling in
DispatchVoiceMode to set status.Tts to Disconnected before the next retry/poll,
while keeping the existing Unknown on first probe and Connected on successful
announcement flow.

---

Outside diff comments:
In `@Resgrid.Audio.Core/WatcherAudioStorage.cs`:
- Around line 19-26: The concern is the static `_watcherAudio` field in
`WatcherAudioStorage`, but this class is created once directly from `Program`
and not through DI, so the current singleton-like lifecycle is safe. Keep
`_watcherAudio` as static and initialized in the `WatcherAudioStorage(ILogger
logger)` constructor; no scoped-instance refactor is needed. If you want to make
the assumption clearer, add a brief note near the field or constructor
referencing the single-instance relay lifecycle used by `Program`.

---

Nitpick comments:
In `@Resgrid.Audio.Relay/Converters/BoolToVisibilityConverter.cs`:
- Around line 15-17: The shared singleton in BoolToVisibilityConverter should be
immutable so its state cannot be changed after initialization. Update the Invert
property on BoolToVisibilityConverter to use an init-only accessor instead of a
set accessor, keeping Instance as the shared static entry point and preventing
later mutation from consumers that reuse it.

In `@Resgrid.Audio.Relay/ShellWindow.xaml.cs`:
- Around line 143-146: The GetPage fallback is silently creating pages without
DI when a type is not registered, which hides configuration errors and can leave
views without a DataContext or _viewModel; update GetPage in ShellWindow.xaml.cs
to fail fast or explicitly log when _provider.GetService(pageType) returns null
instead of always falling back to Activator.CreateInstance(pageType), so missing
registrations surface immediately during development.

In `@Resgrid.Audio.Relay/Views/DevicesView.xaml.cs`:
- Line 6: The XML summary on DevicesView is stale and still describes the view
as a placeholder even though the bindings and UI are implemented. Update the
summary comment in DevicesView.xaml.cs to reflect the actual purpose of the
DevicesView class, keeping it accurate and concise so it matches the current
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89e8d375-70c6-4187-ac9c-594e4cada608

📥 Commits

Reviewing files that changed from the base of the PR and between 4b98065 and d3325aa.

📒 Files selected for processing (75)
  • Directory.Build.props
  • Resgrid.Audio.Core/AudioEvaluator.cs
  • Resgrid.Audio.Core/Radio/RadioBridge.cs
  • Resgrid.Audio.Core/WatcherAudioStorage.cs
  • Resgrid.Audio.Relay/App.xaml
  • Resgrid.Audio.Relay/App.xaml.cs
  • Resgrid.Audio.Relay/Controls/LevelMeter.xaml
  • Resgrid.Audio.Relay/Controls/LevelMeter.xaml.cs
  • Resgrid.Audio.Relay/Controls/Sparkline.cs
  • Resgrid.Audio.Relay/Controls/StatusPill.xaml
  • Resgrid.Audio.Relay/Controls/StatusPill.xaml.cs
  • Resgrid.Audio.Relay/Converters/BoolToBrushConverter.cs
  • Resgrid.Audio.Relay/Converters/BoolToVisibilityConverter.cs
  • Resgrid.Audio.Relay/Converters/BoolToVisibilityInvertedConverter.cs
  • Resgrid.Audio.Relay/Converters/ConnectionStateToBrushConverter.cs
  • Resgrid.Audio.Relay/Converters/CountToVisibilityConverter.cs
  • Resgrid.Audio.Relay/Converters/LogLevelToBrushConverter.cs
  • Resgrid.Audio.Relay/Converters/RelayServiceStateToBrushConverter.cs
  • Resgrid.Audio.Relay/Converters/SecretMaskConverter.cs
  • Resgrid.Audio.Relay/Converters/SquelchTextConverter.cs
  • Resgrid.Audio.Relay/Converters/StatusBrushes.cs
  • Resgrid.Audio.Relay/Converters/TuneButtonTextConverter.cs
  • Resgrid.Audio.Relay/MainWindow.xaml
  • Resgrid.Audio.Relay/MainWindow.xaml.cs
  • Resgrid.Audio.Relay/Properties/Resources.Designer.cs
  • Resgrid.Audio.Relay/Properties/Resources.resx
  • Resgrid.Audio.Relay/Properties/Settings.Designer.cs
  • Resgrid.Audio.Relay/Properties/Settings.settings
  • Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj
  • Resgrid.Audio.Relay/Resources/AppDictionary.xaml
  • Resgrid.Audio.Relay/Services/ConfigurationService.cs
  • Resgrid.Audio.Relay/Services/DeviceEnumerationService.cs
  • Resgrid.Audio.Relay/Services/RelayController.cs
  • Resgrid.Audio.Relay/ShellWindow.xaml
  • Resgrid.Audio.Relay/ShellWindow.xaml.cs
  • Resgrid.Audio.Relay/Skins/MainSkin.xaml
  • Resgrid.Audio.Relay/ViewModel/MainWindowViewModel.cs
  • Resgrid.Audio.Relay/ViewModel/ShuttingDownMessage.cs
  • Resgrid.Audio.Relay/ViewModel/ViewModelLocator.cs
  • Resgrid.Audio.Relay/ViewModels/AboutViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/ConfigurationViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/DashboardViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/DevicesViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/LogsViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/ModeCardViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/OperationsViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/ServiceTileViewModel.cs
  • Resgrid.Audio.Relay/ViewModels/ShellViewModel.cs
  • Resgrid.Audio.Relay/Views/AboutView.xaml
  • Resgrid.Audio.Relay/Views/AboutView.xaml.cs
  • Resgrid.Audio.Relay/Views/ConfigurationView.xaml
  • Resgrid.Audio.Relay/Views/ConfigurationView.xaml.cs
  • Resgrid.Audio.Relay/Views/DashboardView.xaml
  • Resgrid.Audio.Relay/Views/DashboardView.xaml.cs
  • Resgrid.Audio.Relay/Views/DevicesView.xaml
  • Resgrid.Audio.Relay/Views/DevicesView.xaml.cs
  • Resgrid.Audio.Relay/Views/LogsView.xaml
  • Resgrid.Audio.Relay/Views/LogsView.xaml.cs
  • Resgrid.Audio.Relay/Views/OperationsView.xaml
  • Resgrid.Audio.Relay/Views/OperationsView.xaml.cs
  • Resgrid.Relay.Engine/Configuration/RelayConfiguration.cs
  • Resgrid.Relay.Engine/Logging/LogRecord.cs
  • Resgrid.Relay.Engine/Logging/UiBusSinkExtensions.cs
  • Resgrid.Relay.Engine/Logging/UiLogBus.cs
  • Resgrid.Relay.Engine/Logging/UiLogSink.cs
  • Resgrid.Relay.Engine/Services/AudioImportService.cs
  • Resgrid.Relay.Engine/Services/DispatchRelayService.cs
  • Resgrid.Relay.Engine/Services/RadioRelayService.cs
  • Resgrid.Relay.Engine/Services/RecordRelayService.cs
  • Resgrid.Relay.Engine/Services/RelayServiceBase.cs
  • Resgrid.Relay.Engine/Services/RelayServiceFactory.cs
  • Resgrid.Relay.Engine/Services/SmtpRelayService.cs
  • Resgrid.Relay.Engine/Voice/AudioImportMode.cs
  • Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs
  • Resgrid.Relay.Engine/Voice/RadioMode.cs
💤 Files with no reviewable changes (11)
  • Resgrid.Audio.Relay/Properties/Settings.settings
  • Resgrid.Audio.Relay/ViewModel/MainWindowViewModel.cs
  • Resgrid.Audio.Relay/ViewModel/ViewModelLocator.cs
  • Resgrid.Audio.Relay/Resources/AppDictionary.xaml
  • Resgrid.Audio.Relay/Skins/MainSkin.xaml
  • Resgrid.Audio.Relay/ViewModel/ShuttingDownMessage.cs
  • Resgrid.Audio.Relay/Properties/Resources.Designer.cs
  • Resgrid.Audio.Relay/MainWindow.xaml
  • Resgrid.Audio.Relay/Properties/Settings.Designer.cs
  • Resgrid.Audio.Relay/Properties/Resources.resx
  • Resgrid.Audio.Relay/MainWindow.xaml.cs

Comment on lines +82 to +89
// UI log bus + a Serilog logger fanned out to it (plus Debug for local diagnostics).
var logBus = new UiLogBus();
services.AddSingleton(logBus);

ILogger logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.UiBus(logBus)
.CreateLogger();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale comment: no Debug sink is configured.

The comment claims the logger is fanned out "plus Debug for local diagnostics," but the LoggerConfiguration only wires WriteTo.UiBus(logBus). Either drop the parenthetical or add the WriteTo.Debug() sink to match intent.

📝 Option A — fix the comment
-			// UI log bus + a Serilog logger fanned out to it (plus Debug for local diagnostics).
+			// UI log bus + a Serilog logger fanned out to it.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// UI log bus + a Serilog logger fanned out to it (plus Debug for local diagnostics).
var logBus = new UiLogBus();
services.AddSingleton(logBus);
ILogger logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.UiBus(logBus)
.CreateLogger();
// UI log bus + a Serilog logger fanned out to it.
var logBus = new UiLogBus();
services.AddSingleton(logBus);
ILogger logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.UiBus(logBus)
.CreateLogger();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/App.xaml.cs` around lines 82 - 89, The logging setup in
App.xaml.cs is inconsistent with its comment because the LoggerConfiguration in
the logger initialization only uses WriteTo.UiBus(logBus) and does not include a
Debug sink. Update the implementation to match the intended behavior by either
adding the missing WriteTo.Debug() sink in the LoggerConfiguration chain or
removing the “plus Debug for local diagnostics” wording from the comment so it
accurately reflects the actual sinks configured.

Comment thread Resgrid.Audio.Relay/Controls/LevelMeter.xaml.cs Outdated
Comment thread Resgrid.Audio.Relay/Controls/Sparkline.cs
Comment thread Resgrid.Audio.Relay/Controls/StatusPill.xaml Outdated
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="NAudio" Version="1.10.0" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all NAudio references (PackageReference and HintPath/Reference) across the solution.
rg -nP --type-add 'proj:*.csproj' -t proj 'NAudio' -g '!**/bin/**' -g '!**/obj/**'
# Surface any checked-in NAudio binaries/package folders.
fd -i 'naudio' -t f -e dll
fd -i 'naudio' -t d

Repository: Resgrid/Relay

Length of output: 211


🏁 Script executed:

#!/bin/bash
# Search for NAudio references in csproj files
echo "=== searching csproj for NAudio ==="
find . -name "*.csproj" -type f | xargs grep -i "NAudio" 2>/dev/null

# Search for any existing NAudio package folders (old style)
echo "=== searching for packages/NAudio* folders ==="
ls -d packages/NAudio* 2>/dev/null || echo "No packages/NAudio* folder found at root"

# Check Resgrid.Audio.Relay.csproj specifically
echo "=== Resgrid.Audio.Relay.csproj content ==="
cat Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj

Repository: Resgrid/Relay

Length of output: 2873


🏁 Script executed:

#!/bin/bash
# Search for NAudio references in csproj files
echo "=== searching csproj for NAudio ==="
find . -name "*.csproj" -type f | xargs grep -i "NAudio" 2>/dev/null

# Search for any existing NAudio package folders (old style)
echo "=== searching for packages/NAudio* folders ==="
ls -d packages/NAudio* 2>/dev/null || echo "No packages/NAudio* folder found at root"

# Check Resgrid.Audio.Relay.csproj specifically
echo "=== Resgrid.Audio.Relay.csproj content ==="
cat Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj

Repository: Resgrid/Relay

Length of output: 2873


🏁 Script executed:

#!/bin/bash

# Verify the existence of the References folder and check its contents for NAudio binaries
# The guideline mentions: "Windows audio path depends on checked-in binary references from References\ and packages\NAudio.1.8.4\"
# We need to determine if the old 1.8.4 binaries still exist or if the project has fully migrated to the 1.10.0 PackageReference

echo "=== Check if References folder exists ==="
ls -la References/ 2>/dev/null || echo "References folder missing"

echo "=== List specific NAudio related items in References ==="
find References/ -name "*naudio*" -o -name "*NAudio*" 2>/dev/null || echo "No NAudio files found in References"

echo "=== Check Resgrid.Audio.Core references ==="
cat Resgrid.Audio.Core/Resgrid.Audio.Core.csproj

Repository: Resgrid/Relay

Length of output: 1869


NAudio version mismatch with checked-in DtmfDetection binaries

The Resgrid.Audio.Relay.csproj (and dependencies) use NAudio 1.10.0 via PackageReference. However, Resgrid.Audio.Core references DtmfDetection.NAudio.dll from ..\References\, which was likely compiled against the older NAudio 1.8.4 indicated by the project guidelines.

Running NAudio 1.10.0 against a binary built for 1.8.4 may introduce API incompatibilities or runtime binding failures for DTMF detection. Verify that DtmfDetection.NAudio.dll is compatible with NAudio 1.10.0 or update the check-in binary to align with the current package version.

Current Configuration

Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj:

    <PackageReference Include="NAudio" Version="1.10.0" />

Resgrid.Audio.Core/Resgrid.Audio.Core.csproj:

    <PackageReference Include="NAudio" Version="1.10.0" />
    <Reference Include="DtmfDetection.NAudio">
      <HintPath>..\References\DtmfDetection.NAudio.dll</HintPath>
    </Reference>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/Resgrid.Audio.Relay.csproj` at line 24, The NAudio
dependency version in Resgrid.Audio.Relay/Resgrid.Audio.Core is out of sync with
the checked-in DtmfDetection.NAudio binary. Verify DtmfDetection.NAudio.dll is
compatible with NAudio 1.10.0; if not, rebuild or replace the binary so it
matches the current NAudio package version. Update the references in
Resgrid.Audio.Core and any related project settings so the DtmfDetection path
and NAudio PackageReference stay aligned.

Source: Coding guidelines


namespace Resgrid.Audio.Relay.Views
{
/// <summary>Placeholder About screen — filled in by the next UI pass.</summary>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale doc comment. The summary calls this a "Placeholder About screen — filled in by the next UI pass," but the view is fully implemented (version, startup toggle, links). Update or remove the comment to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resgrid.Audio.Relay/Views/AboutView.xaml.cs` at line 6, The summary on
AboutView is stale and still describes the view as a placeholder even though the
page is implemented. Update or remove the XML doc comment on AboutView in
AboutView.xaml.cs so it accurately reflects the current behavior and features
exposed by the view, keeping the description aligned with the actual UI instead
of the old placeholder wording.

Comment thread Resgrid.Audio.Relay/Views/LogsView.xaml.cs Outdated
Comment thread Resgrid.Relay.Engine/Services/RelayServiceBase.cs Outdated
Comment thread Resgrid.Relay.Engine/Services/RelayServiceFactory.cs Outdated
Comment thread Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs
Comment thread Resgrid.Audio.Relay/ViewModels/ConfigurationViewModel.cs Outdated
Comment thread Resgrid.Audio.Relay/ViewModels/LogsViewModel.cs Outdated
@Resgrid-Bot

This comment has been minimized.

Comment thread Resgrid.Relay.Engine/Voice/DispatchVoiceMode.cs
@Resgrid-Bot

Resgrid-Bot commented Jun 25, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@ucswift ucswift merged commit 4edaa95 into master Jun 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants