@@ -141,12 +141,14 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
141141 the current destination's leave effects first (leaving
142142 Profiles awaits the unsaved-changes three-choice
143143 guard, Cancel/Save-failure keeps the destination
144- unchanged; leaving Nexus
145- calls `IntegrationsViewModel.Deactivate` which cancels
146- the in-flight auth + the shell re-reads nxm status +
147- reloads the mod list; leaving Settings reloads the mod
148- list + re-reads the startup-check toggle + refreshes the
149- app-update notice), then switches the destination, then
144+ unchanged; leaving Nexus
145+ calls `IntegrationsViewModel.Deactivate` which cancels
146+ the in-flight auth + the shell reloads the mod list
147+ (no nxm probe on leave; the registration state
148+ refreshes at Nexus ENTER, its deliberate probe
149+ point); leaving Settings reloads the mod
150+ list + re-reads the startup-check toggle + refreshes the
151+ app-update notice), then switches the destination, then
150152 runs the target's enter effects (Settings calls
151153 `SettingsViewModel.RefreshFromConfig` synchronously;
152154 Nexus awaits `IntegrationsViewModel.RefreshAsync`
@@ -166,7 +168,9 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
166168 (bounded detector handoff, no process handle; the
167169 state clears in all completion/exception paths)), and
168170 the global status strip (running + pending + nxm-handler
169- + app-update notice). The hosted page VMs are
171+ + app-update notice; the nxm indicator mirrors the
172+ shared `INxmRegistrationState`, seeded by its one
173+ startup probe + updated on each publish). The hosted page VMs are
170174 application-lifetime singletons; navigation never calls an
171175 old Window-close Detach path. The active profile is owned
172176 by `IProfileSession`; launch availability derives directly
@@ -396,11 +400,16 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
396400 usable while Darktide runs (only launch + active-profile
397401 changes are blocked); the destination also owns the
398402 explicit `nxm://` handler registration (a "Nexus download
399- links" section over `INxmHandlerRegistrar`: register
400- confirms first since it is a system-wide change that can
401- affect other mod managers; unregister only releases
402- Curator's own registration); entering the destination
403- refreshes auth state, leaving cancels in-flight auth via
403+ links" section over `INxmHandlerRegistrar` for the
404+ mutations + the shared `INxmRegistrationState` for the
405+ status: register confirms first since it is a system-wide
406+ change that can affect other mod managers; unregister
407+ delegates straight to the self-guarded registrar, which
408+ releases only Curator's own registration; after either
409+ action one refresh publishes the state to every
410+ consumer); entering the destination
411+ refreshes auth state (one registration probe per
412+ enter), leaving cancels in-flight auth via
404413 `Deactivate`;
405414 the Preferences destination (`PreferencesViewModel` +
406415 `PreferencesView`): theme + font scale + language + the
@@ -455,6 +464,22 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
455464 coordinating IDialogService + IProfileSession + Dispatcher.UIThread) that
456465 replaces the no-op default via DI last-registration-wins, registered after
457466 AddNxm() in CuratorComposition;
467+ the shared `INxmRegistrationState` (ui/Session/, an
468+ application-lifetime singleton): the last-known OS
469+ `nxm://` registration for every UI surface (shell
470+ status strip, Mods empty-state hint, Nexus page, DMF
471+ prompt wording). `RefreshFromOs` is its only writer
472+ + the UI's only probe: one seed at shell
473+ construction, one per Nexus enter, one after each
474+ register/release action; each publishes `Changed`
475+ (marshaled to the UI thread) so every surface
476+ updates together. All other consumers read
477+ last-known + accept staleness (the OS association is
478+ racy by nature); `ModListViewModel.Reload()` and all
479+ navigation-leave effects perform zero probes;
480+ `DmfPromptService` reads the state + never probes;
481+ only `IntegrationsViewModel` still injects the
482+ registrar (for the register/release mutations);
458483 `UpdateCheckRunner` (ui/Session/) the
459484 UI-layer glue that fires `IUpdateCheckService.CheckAsync`
460485 fire-and-forget on the three automatic triggers
@@ -636,9 +661,11 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
636661 profile that becomes active (no persisted flag: a
637662 fresh ask per profile). Two cases: DMF in the repo
638663 but not the profile -> instant add (case 1); DMF not
639- in the repo -> a download confirm (the message
640- tailors to whether Curator owns the `nxm://` handler:
641- manager-download vs. manual-import guidance); on
664+ in the repo -> a download confirm (the message
665+ tailors to whether Curator owns the `nxm://` handler,
666+ read from the shared `INxmRegistrationState` with no
667+ probe:
668+ manager-download vs. manual-import guidance); on
642669 confirm, premium users get the in-app API download
643670 under a spinner + add, while everyone else (no auth,
644671 regular, or unknown premium state) gets the DMF Nexus
@@ -796,12 +823,11 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
796823 (INexusClient over the v1 REST endpoints with per-request
797824 auth via INexusAuthMessageFactory selector -- ApiKey /
798825 OAuth / None factories, the latter doing 401-reactive
799- refresh; NexusAuthService the OAuth loopback + API-key
800- validate + sign-out orchestrator (raises
801- AuthStateChanged on every persisted method change so
802- the shell's Integrations flow refreshes the nxm handler
803- status after the dialog closes; the DMF prompt is
804- profile-creation-only and does not subscribe); NexusOAuthTokenStore
826+ refresh; NexusAuthService the OAuth loopback + API-key
827+ validate + sign-out orchestrator (raises
828+ AuthStateChanged on every persisted method change; the
829+ DMF prompt is
830+ profile-creation-only and does not subscribe); NexusOAuthTokenStore
805831 owns the OidcClient + token persistence; LoopbackBrowser
806832 the IBrowser impl with an HttpListener on an ephemeral
807833 port; Duende.IdentityModel.OidcClient 7.1.0 for the
@@ -958,12 +984,21 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
958984 default (the real handler is registered via AddSingleton
959985 last-wins, in CuratorComposition after AddNxm()), the OS
960986 scheme-handler registrar
961- (INxmHandlerRegistrar: WindowsNxmHandlerRegistrar writes
962- HKCU\Software\Classes\nxm; LinuxNxmHandlerRegistrar writes a .desktop
963- file + xdg-mime default; AppImage registration atomically copies the
964- handler to a durable per-user directory + creates a sibling symlink
965- to $APPIMAGE; startup maintenance refreshes those files only while
966- Curator owns the active association), + NxmHandlerRelay (the testable core the
987+ (INxmHandlerRegistrar: WindowsNxmHandlerRegistrar writes
988+ HKCU\Software\Classes\nxm; LinuxNxmHandlerRegistrar writes a .desktop
989+ file + xdg-mime default; every xdg-mime invocation runs sanitized:
990+ the child's env is the parent's with ONLY LD_PRELOAD removed
991+ (Steam's overlay preload slows host utilities ~10x; Curator's own
992+ env untouched), while the wait stays plain + synchronous (a hung
993+ desktop helper hangs the probe rather than being masked:
994+ deliberate, fail loud); AppImage registration atomically copies the
995+ handler to a durable per-user directory + creates a sibling symlink
996+ to $APPIMAGE; startup maintenance refreshes those files only while
997+ Curator owns the active association; Unregister is self-guarded on
998+ both platforms: it never removes another program's registration +
999+ touches only Curator's own registration files (a no-op or a
1000+ removal of Curator's own files depending on platform state), so
1001+ callers never pre-check), + NxmHandlerRelay (the testable core the
9671002 handler exe calls: hot-path IPC delivery + cold-start launch+retry,
9681003 UseShellExecute=false on both OSes). AOT-friendly (IsAotCompatible;
9691004 only raw byte/UTF-8 IO in the handler path).
@@ -1045,9 +1080,17 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
10451080 dirty-Profiles-draft navigation cancellation, entering
10461081 Settings rehydrates + leaving Settings runs the mod-list
10471082 + app-update refresh, entering Integrations refreshes +
1048- leaving cancels auth + refreshes nxm/mod-list, Launch
1083+ leaving cancels auth + reloads the mod list with zero
1084+ registration refreshes on any leave, exactly one seed
1085+ refresh at shell construction + the strip following a
1086+ shared-state publish, Launch
10491087 CanExecute + execution following
10501088 IProfileSession.ActiveProfileId directly) + the
1089+ NxmRegistrationStateTests (the production shared-state
1090+ contract: unavailable-without-registrar publishes, the
1091+ registrar read on refresh, a probe throw treated as
1092+ not-registered, Changed marshaled through the UI seam)
1093+ + the
10511094 ShellLaunchAttemptTests (the launch-attempt state via
10521095 deterministic yield + timeout seams: attempt set +
10531096 CanExecute false before the launch service runs, false
@@ -1110,7 +1153,9 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
11101153 browser-open, the new-profile trigger, the
11111154 decline path, the premium in-app download,
11121155 the non-premium/unknown/no-auth browser-open
1113- regardless of the nxm registrar state, and the
1156+ regardless of the registration state (the
1157+ confirm wording follows the shared state
1158+ with zero probes), and the
11141159 prompt-timing-after-create)
11151160 + the OnboardingService (already complete no-op,
11161161 Continue persists + skips Integrations, Set up Nexus
@@ -1133,8 +1178,13 @@ src/ Modificus Curator -- the mod manager app (.NET 10 + Avalonia 12)
11331178 Minimized/FullScreen)
11341179 Modificus.Curator.Nxm.Tests/ xUnit tests for the nxm library (parser, framing,
11351180 IPC server resilience, SingleInstanceGuard, router,
1136- relay helper, standalone + AppImage Linux registrar,
1137- owned-registration maintenance, AddNxm wiring;
1181+ relay helper, standalone + AppImage Linux registrar
1182+ (incl. the child-env sanitizer dropping exactly
1183+ LD_PRELOAD), owned-registration
1184+ maintenance, the Windows registrar's self-guarded
1185+ unregister (absent no-op / foreign preserved / own
1186+ deleted, via the base-key seam over a temp subkey;
1187+ Windows-gated), AddNxm wiring;
11381188 serialized via DisableTestParallelization since
11391189 real named pipes are an OS-level shared resource)
11401190docs/ architecture/ + reference/ (src/ per-library API refs + the release strategy reference)
0 commit comments