Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ SettingsStore atomic file replacement
↓
Current.ReplaceWith
↓
one synchronous Changed event
one synchronous Changed(origin) event
```

A failed mutation or failed write does not replace committed settings and does not publish a settings change. `SettingsCoordinator.Current` exposes `IReadOnlySightAdaptSettings` and returns a defensive snapshot. Mutable `SightAdaptSettings` is confined to explicit transaction callbacks and normalization/persistence boundaries. `SettingsUseCaseContext` centralizes snapshot, change-event and commit plumbing for application use-case façades without sharing their domain commands. `Commit` and `PersistCurrent` use one persistence execution path; only `Commit` publishes the synchronous `Changed` event after a successful write.

Every published `SettingsChangedEventArgs` carries the explicit change origin supplied by the transaction boundary. A use-case context marks its own commits with a private origin token; local presentation code performs its one explicit post-commit follow-up and ignores only that origin, while external changes still refresh it. `RuntimeCoordinator` uses itself as the origin for runtime commands and ignores only its own generic callback because those commands already perform their runtime follow-up. Nested commits retain independent origins, so reentrant event delivery does not rely on component Boolean suppression flags.

## Authorities

| Concern | Authority |
|---|---|
| Settings transaction and published read-only snapshots | `SettingsCoordinator` |
| Settings transaction, change origin and published read-only snapshots | `SettingsCoordinator` |
| Shared use-case snapshot/event/commit plumbing | `SettingsUseCaseContext` |
| Settings JSON persistence and atomic replacement | `SettingsStore` |
| Persisted JSON DTOs and legacy-field migration | `PersistedSettingsMapper` |
Expand Down
31 changes: 17 additions & 14 deletions src/SightAdapt/ConfigurationForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ private readonly Action<IWin32Window, SettingsCoordinator>
private readonly ApplicationAssignmentsGrid _assignmentsGrid;
private readonly ModernButton _editVisualProfileButton;
private bool _refreshing;
private bool _committingGridValue;

public ConfigurationForm(
SettingsCoordinator settingsCoordinator,
Expand Down Expand Up @@ -445,7 +444,11 @@ private void AutomaticModeCheckedChanged(object? sender, EventArgs eventArgs)
{
ShowCommitError(result.ErrorMessage);
RefreshProfiles();
return;
}

UpdateAutomaticModeState(
_automaticModeSwitch.Checked);
}

private void UpdateAutomaticModeState(bool automaticMode)
Expand Down Expand Up @@ -474,17 +477,7 @@ private void AssignmentChanged(
var displayedRow =
ApplicationAssignmentRowMapper.Map(
displayedAssignment);
SettingsCommitResult result;

_committingGridValue = true;
try
{
result = _useCases.Apply(change);
}
finally
{
_committingGridValue = false;
}
var result = _useCases.Apply(change);

if (!result.Succeeded)
{
Expand Down Expand Up @@ -557,7 +550,10 @@ private void EditSelectedVisualProfile()
{
ShowCommitError(result.ErrorMessage);
RefreshProfiles();
return;
}

RefreshProfiles();
}

internal void ManageVisualProfiles()
Expand Down Expand Up @@ -638,6 +634,8 @@ private void AddOrUpdateProfile(ApplicationIdentity identity)
return;
}

RefreshProfiles();

MessageBox.Show(
this,
result.Value
Expand Down Expand Up @@ -671,12 +669,17 @@ private void RemoveSelectedProfile()
if (!result.Succeeded)
{
ShowCommitError(result.ErrorMessage);
return;
}

RefreshProfiles();
}

private void SettingsChanged(object? sender, EventArgs eventArgs)
private void SettingsChanged(
object? sender,
SettingsChangedEventArgs eventArgs)
{
if (_committingGridValue)
if (_useCases.IsLocalChange(eventArgs))
{
return;
}
Expand Down
6 changes: 5 additions & 1 deletion src/SightAdapt/ConfigurationUseCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ internal ConfigurationUseCases(
public string SettingsPath =>
_settings.SettingsPath;

public event EventHandler? Changed
public event EventHandler<SettingsChangedEventArgs>? Changed
{
add => _settings.Changed += value;
remove => _settings.Changed -= value;
}

public bool IsLocalChange(
SettingsChangedEventArgs eventArgs) =>
_settings.IsLocalChange(eventArgs);

public SettingsCommitResult SetAutomaticMode(bool enabled)
{
return _settings.Commit(settings =>
Expand Down
24 changes: 11 additions & 13 deletions src/SightAdapt/RuntimeCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ internal sealed class RuntimeCoordinator
private readonly RuntimeOverlayActivator _overlayActivator;
private readonly AutomaticActivationService _automaticActivation;
private readonly Func<IReadOnlySightAdaptSettings> _readSettings;
private bool _committingSettings;

internal RuntimeCoordinator(
SettingsCoordinator settingsCoordinator,
Expand Down Expand Up @@ -187,14 +186,19 @@ public void HandleForegroundWindowChanged(

public void HandleSettingsChanged()
{
HandleSettingsChanged(ReadSettings());
HandleSettingsChanged(
ReadSettings(),
eventArgs: null);
}

internal void HandleSettingsChanged(
IReadOnlySightAdaptSettings settings)
IReadOnlySightAdaptSettings settings,
SettingsChangedEventArgs? eventArgs)
{
ArgumentNullException.ThrowIfNull(settings);
if (_committingSettings)
if (ReferenceEquals(
eventArgs?.Origin,
this))
{
return;
}
Expand Down Expand Up @@ -248,15 +252,9 @@ private SettingsCommitResult<T> CommitSettings<T>(
{
ArgumentNullException.ThrowIfNull(mutation);

_committingSettings = true;
try
{
return _settingsCoordinator.Commit(mutation);
}
finally
{
_committingSettings = false;
}
return _settingsCoordinator.Commit(
mutation,
origin: this);
}

private void ShowCommitError(string? message)
Expand Down
39 changes: 27 additions & 12 deletions src/SightAdapt/SettingsCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public static SettingsCommitResult Failure(string message) =>
new(false, message);
}

internal sealed class SettingsChangedEventArgs(
object? origin) : EventArgs
{
public object? Origin { get; } = origin;
}

internal sealed class SettingsCommitResult<T>
{
private readonly T _value;
Expand Down Expand Up @@ -74,18 +80,21 @@ public SettingsCoordinator(

public bool SettingsWereMigrated => _store.SettingsWereMigrated;

public event EventHandler? Changed;
public event EventHandler<SettingsChangedEventArgs>? Changed;

public SettingsCommitResult Commit(
Action<SightAdaptSettings> mutation)
Action<SightAdaptSettings> mutation,
object? origin = null)
{
ArgumentNullException.ThrowIfNull(mutation);

var result = Commit<object?>(settings =>
{
mutation(settings);
return null;
});
var result = Commit<object?>(
settings =>
{
mutation(settings);
return null;
},
origin);

return result.Succeeded
? SettingsCommitResult.Success()
Expand All @@ -95,19 +104,22 @@ public SettingsCommitResult Commit(
}

public SettingsCommitResult<T> Commit<T>(
Func<SightAdaptSettings, T> mutation)
Func<SightAdaptSettings, T> mutation,
object? origin = null)
{
ArgumentNullException.ThrowIfNull(mutation);
return ExecuteTransaction(
mutation,
publishChanged: true);
publishChanged: true,
origin);
}

public SettingsCommitResult PersistCurrent()
{
var result = ExecuteTransaction<object?>(
_ => null,
publishChanged: false);
publishChanged: false,
origin: null);

return result.Succeeded
? SettingsCommitResult.Success()
Expand All @@ -118,7 +130,8 @@ public SettingsCommitResult PersistCurrent()

private SettingsCommitResult<T> ExecuteTransaction<T>(
Func<SightAdaptSettings, T> operation,
bool publishChanged)
bool publishChanged,
object? origin)
{
ArgumentNullException.ThrowIfNull(operation);

Expand All @@ -145,7 +158,9 @@ private SettingsCommitResult<T> ExecuteTransaction<T>(
_current.ReplaceWith(candidate);
if (publishChanged)
{
Changed?.Invoke(this, EventArgs.Empty);
Changed?.Invoke(
this,
new SettingsChangedEventArgs(origin));
}

return SettingsCommitResult<T>.Success(value);
Expand Down
20 changes: 17 additions & 3 deletions src/SightAdapt/SettingsUseCaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace SightAdapt;
internal sealed class SettingsUseCaseContext
{
private readonly SettingsCoordinator _coordinator;
private readonly object _changeOrigin = new();

public SettingsUseCaseContext(
SettingsCoordinator coordinator)
Expand All @@ -17,17 +18,30 @@ public SettingsUseCaseContext(
public string SettingsPath =>
_coordinator.SettingsPath;

public event EventHandler? Changed
public event EventHandler<SettingsChangedEventArgs>? Changed
{
add => _coordinator.Changed += value;
remove => _coordinator.Changed -= value;
}

public bool IsLocalChange(
SettingsChangedEventArgs eventArgs)
{
ArgumentNullException.ThrowIfNull(eventArgs);
return ReferenceEquals(
eventArgs.Origin,
_changeOrigin);
}

public SettingsCommitResult Commit(
Action<SightAdaptSettings> mutation) =>
_coordinator.Commit(mutation);
_coordinator.Commit(
mutation,
_changeOrigin);

public SettingsCommitResult<T> Commit<T>(
Func<SightAdaptSettings, T> mutation) =>
_coordinator.Commit(mutation);
_coordinator.Commit(
mutation,
_changeOrigin);
}
6 changes: 4 additions & 2 deletions src/SightAdapt/SightAdaptContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,16 @@ private void ForegroundWindowChanged(

private void SettingsChanged(
object? sender,
EventArgs eventArgs)
SettingsChangedEventArgs eventArgs)
{
var settings = _settingsCoordinator.Current;
_tray.SetAutomaticMode(settings.AutomaticMode);
ApplyApplicationState(
_stateController.Current,
settings);
_runtimeCoordinator.HandleSettingsChanged(settings);
_runtimeCoordinator.HandleSettingsChanged(
settings,
eventArgs);
}

private void ShowConfiguration()
Expand Down
22 changes: 8 additions & 14 deletions src/SightAdapt/VisualProfileManagerForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ internal sealed class VisualProfileManagerForm : Form
private readonly ModernButton _renameButton;
private readonly ModernButton _editButton;
private readonly ModernButton _deleteButton;
private bool _committingLocalChange;

internal VisualProfileManagerForm(
SettingsCoordinator settingsCoordinator)
Expand Down Expand Up @@ -404,16 +403,7 @@ internal void Commit(
{
ArgumentNullException.ThrowIfNull(command);

SettingsCommitResult<string> result;
_committingLocalChange = true;
try
{
result = command(_useCases);
}
finally
{
_committingLocalChange = false;
}
var result = command(_useCases);

if (result.Succeeded)
{
Expand All @@ -430,12 +420,16 @@ internal void Commit(
RefreshProfiles();
}

private void SettingsChanged(object? sender, EventArgs eventArgs)
private void SettingsChanged(
object? sender,
SettingsChangedEventArgs eventArgs)
{
if (!_committingLocalChange)
if (_useCases.IsLocalChange(eventArgs))
{
RefreshProfiles();
return;
}

RefreshProfiles();
}

}
6 changes: 5 additions & 1 deletion src/SightAdapt/VisualProfileUseCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ internal VisualProfileUseCases(
public IReadOnlySightAdaptSettings Snapshot =>
_settings.Snapshot;

public event EventHandler? Changed
public event EventHandler<SettingsChangedEventArgs>? Changed
{
add => _settings.Changed += value;
remove => _settings.Changed -= value;
}

public bool IsLocalChange(
SettingsChangedEventArgs eventArgs) =>
_settings.IsLocalChange(eventArgs);

public string CreateAvailableName(string baseName)
{
var snapshot = Snapshot;
Expand Down
6 changes: 4 additions & 2 deletions tests/SightAdapt.Tests/RuntimeCoordinatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ public void DisableAutomaticMode()

public void WireSettingsChanged()
{
Settings.Changed += (_, _) =>
Coordinator.HandleSettingsChanged();
Settings.Changed += (_, eventArgs) =>
Coordinator.HandleSettingsChanged(
Settings.Current,
eventArgs);
}

public void Dispose()
Expand Down
Loading
Loading