Skip to content

Fix/code review issues#44

Merged
NeverMorewd merged 17 commits intomainfrom
fix/code-review-issues
Mar 20, 2026
Merged

Fix/code review issues#44
NeverMorewd merged 17 commits intomainfrom
fix/code-review-issues

Conversation

@NeverMorewd
Copy link
Copy Markdown
Owner

No description provided.

NeverMorewd and others added 17 commits March 20, 2026 09:24
…tcNow

DateTime.UtcNow causes an implicit conversion when assigned to a DateTimeOffset,
which loses the explicit UTC offset semantics. DateTimeOffset.UtcNow is the
correct and explicit way to capture the current UTC time as a DateTimeOffset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…served exceptions

The window-based FrontShowAsync overload used fire-and-forget `_ = HandleCloseInternalAsync(...)`
while the view-based overload correctly awaited it. This meant any exception thrown during
dialog close (OnDialogClosingAsync, OnDialogClosedAsync) would be silently lost as an
UnobservedTaskException. Fixed to await the close task consistently with the view overload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The faulted task branch only read t.Exception to mark it observed but discarded the
information entirely. Exceptions from fire-and-forget Show() dialog operations would
vanish without any trace, making them impossible to diagnose. Now logs the exception
via Debug.WriteLine so they are at least visible during development.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Manager

Methods that return Task must follow the Async naming convention per .NET guidelines.
IRegionManager had GoForward/GoBack without the Async suffix, while IRegion correctly
used GoForwardAsync/GoBackAsync - causing inconsistency within the same framework.

Updated all call sites in samples and tests accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… deprecation

The XML docs already stated MaxCachedViews was obsolete, but without the [Obsolete]
attribute the compiler emits no warning, making the deprecation notice invisible to
callers. Added the attribute so IDEs and the compiler surface the warning automatically.
Internal usages are suppressed with #pragma warning disable CS0618.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Members (missing 'h')

The public API methods had a typo: 'Wit' instead of 'With'. Introduced correctly-spelled
AddSingletonWithAllMembers and AddTransientWithAllMembers as the canonical versions, and
marked the misspelled originals with [Obsolete] pointing to the new names to preserve
backwards compatibility without breaking existing callers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…avigationService

'Resovle' was a transposition typo. This is an internal private method so there
is no public API impact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…U race

Two issues fixed:
1. Used non-generic WeakReference which lacks type safety and requires casting.
   Replaced with WeakReference<object> to match the generic API.

2. TOCTOU (time-of-check/time-of-use) race: previously checked IsAlive then
   invoked the handler, but GC could collect the target between the two operations.
   Fixed by using TryGetTarget() to atomically obtain a strong reference, preventing
   the target from being collected while the handler runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on after cancellation

CancelAsync() only signals the CancellationToken; it does not wait for the job task
to actually finish. Without awaiting WaitAllAsync() afterwards, a new navigation job
could start while the previous job is still executing its catch/finally cleanup,
causing concurrent navigation state corruption.

Fixed by awaiting WaitAllAsync() after CancelAllAsync() in the CancelCurrent branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…text arguments

Static factory methods (Success, Failure, Cancelled) were calling
navigationContext.UpdateStatus() as a side effect, which violates the principle
that factory methods should not modify their arguments. Callers cannot predict or
control when the status changes, making the context lifecycle opaque.

Moved all UpdateStatus() calls to the call sites (RegionBase, RegionManagerBase)
so the state transition is explicit and visible at each navigation outcome site.
Factory methods now only read the context's current state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elf-comparison bug

MergeFrom is called as NavigationOptions.Default.MergeFrom(userOptions), meaning
'this == Default'. Comparing other.X against Default.X was actually comparing against
this.X — a self-comparison that would silently fail if Default had already been mutated,
and prevented users from ever restoring a property back to its default value.

Extracted factory default values as internal constants (DefaultMaxHistoryItems, etc.)
and compare against those instead. This ensures the comparison is stable regardless
of the current state of the Default instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onAware boilerplate

INavigationAware has 5 methods and 1 event that all must be implemented, even when
only a subset of the lifecycle is relevant. This forces users to write empty method
bodies throughout their codebase.

NavigationAwareBase provides sensible no-op defaults:
- InitializeAsync/OnNavigatedToAsync/OnNavigatedFromAsync/OnUnloadAsync: do nothing
- IsNavigationTargetAsync: returns true (always reuse cached instance)
- AsyncRequestUnloadEvent: declared; RequestUnloadAsync() helper raises it

Users can now inherit from NavigationAwareBase and only override the methods they need.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed changes)

Previously there was no way for a view model to prevent navigation away from itself.
A common use case is an edit form that should confirm before discarding unsaved changes.

INavigationGuard.CanNavigateAsync() is called during the OnBeforeNavigation pipeline
stage, before OnNavigatedFromAsync. Returning false throws OperationCanceledException,
which causes the framework to revert to the current view (existing cancel behavior).

The guard is checked only when the current view model implements it; no behavior change
for view models that do not implement the interface. Works in combination with
NavigationAwareBase and direct INavigationAware implementations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ogging, analytics)

Previously there was no way to intercept all navigations globally without modifying
every view model. Common needs: authentication redirects, analytics tracking,
request logging, A/B testing redirects.

INavigationInterceptor has two hooks:
- OnNavigatingAsync: runs before the navigation pipeline; throw OperationCanceledException
  to abort (treated as navigation cancellation by the framework)
- OnNavigatedAsync: runs after successful navigation; exceptions are logged but do not
  affect the navigation result

Register via RegisterNavigationInterceptor<T>() extension. Multiple interceptors are
supported and are called in registration order. The interceptors are resolved as
singletons via IEnumerable<INavigationInterceptor>.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cific IAvaloniaDialogService

FrontShowAsync was documented as 'particularly useful in AvaloniaUI' yet lived in the
core IDialogService interface, forcing WPF users to be aware of an Avalonia startup
pattern irrelevant to them. WPF had no equivalent concept in its platform service.

Changes:
- Added IAvaloniaDialogService (AsyncNavigation.Avalonia) extending IDialogService
  with FrontShowViewAsync/FrontShowWindowAsync using clean optional-parameter signatures
- Added AvaloniaDialogService (internal) implementing both interfaces, delegating
  FrontShow calls to the existing DialogService.FrontShowAsync implementation
- Avalonia DI setup now registers AvaloniaDialogService under both IDialogService
  and IAvaloniaDialogService so resolving either returns the same instance
- Updated IDialogService.FrontShowAsync doc comments to point Avalonia users to
  IAvaloniaDialogService without breaking existing callers

WPF users: no change, IDialogService still has FrontShowAsync for cross-platform use.
Avalonia users: resolve IAvaloniaDialogService for FrontShowViewAsync/FrontShowWindowAsync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lass' constraint

Minor follow-up to the IAvaloniaDialogService commit:
- Remove CS0109 warning by removing unnecessary 'new' modifier on interface methods
  (the methods do not hide any members from IDialogService)
- Add 'where TWindow : class' to explicit interface implementations in AvaloniaDialogService
  to satisfy the nullable reference type constraint required by the Func<IDialogResult, TWindow?>
  parameter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NeverMorewd NeverMorewd merged commit 3f272a9 into main Mar 20, 2026
6 of 9 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01d7d7e528

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 64 to +65
await CancelAllAsync();
await WaitAllAsync();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Ignore canceled jobs while draining CancelCurrent queue

In HandleExistingJobs, the CancelCurrent branch now does await WaitAllAsync() immediately after cancellation, but WaitAllAsync() propagates TaskCanceledException/OperationCanceledException from the jobs that were just canceled. When a running job correctly throws on cancellation (e.g., Task.Delay(..., ct)), this exception escapes RunJobAsync before the new job is added, so the replacement navigation never starts. This breaks the core CancelCurrent behavior under normal cancellable workloads.

Useful? React with 👍 / 👎.

Comment on lines +184 to 187
public async Task<NavigationResult> GoForwardAsync(string regionName, CancellationToken cancellationToken = default)
{
var region = GetRegion(regionName);
if (await region.CanGoForwardAsync())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run interceptors for history navigation methods

The new interceptor contract says hooks run for every navigation request, but GoForwardAsync/GoBackAsync bypass the interceptor pipeline and call region history navigation directly. In practice, auth/logging/analytics logic implemented via INavigationInterceptor will run for RequestNavigateAsync but be silently skipped for back/forward actions, creating inconsistent behavior depending on how navigation is triggered.

Useful? React with 👍 / 👎.

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.

1 participant