diff --git a/HellionChat/FontManager.cs b/HellionChat/FontManager.cs
index afb6f04..e0b7453 100644
--- a/HellionChat/FontManager.cs
+++ b/HellionChat/FontManager.cs
@@ -8,12 +8,9 @@ using Dalamud.Interface.Utility;
namespace HellionChat;
-// FontManager's two LogProxy sites both live in static methods
-// (TryGetHellionFontBytes, AddFontWithFallback) that the BuildFonts pipeline
-// invokes; an instance _logger field would be unreachable from those scopes.
-// DI-4 Slice D leaves the class on Plugin.LogProxy and counts it under the
-// "static consumers" bucket alongside EmoteCache / AutoTranslate /
-// MemoryUtil / WrapperUtil.
+// Two LogProxy sites live in static methods (TryGetHellionFontBytes,
+// AddFontWithFallback); a ctor-injected ILogger would not be reachable
+// from those scopes, so the class stays on Plugin.LogProxy.
public class FontManager
{
internal IFontHandle Axis = null!;
@@ -64,9 +61,6 @@ public class FontManager
);
if (stream is null)
{
- // Static method has no instance _logger to reach. The resource-
- // missing path is rare (only fires when the embedded font is
- // stripped from the build), so Plugin.LogProxy is acceptable.
Plugin.LogProxy.Warning(
"Hellion font resource missing — falling back to system default font."
);
@@ -243,11 +237,9 @@ public class FontManager
or ArgumentException
)
{
- // Atlas-toolkit throws span IO and validation failures; routing the
- // wider set through the fallback keeps a corrupt font config from
- // taking down the whole atlas build. Static method has no instance
- // _logger to reach (Plugin.Config-driven font swap, called from
- // BuildFonts).
+ // Atlas-toolkit throws span IO and validation failures; routing
+ // the wider set through the fallback keeps a corrupt font config
+ // from taking down the whole atlas build.
Plugin.LogProxy.Warning(
e,
$"Configured {slot} font failed to load ({e.GetType().Name}), "
diff --git a/HellionChat/GameFunctions/GameFunctions.cs b/HellionChat/GameFunctions/GameFunctions.cs
index 5e48942..c807760 100755
--- a/HellionChat/GameFunctions/GameFunctions.cs
+++ b/HellionChat/GameFunctions/GameFunctions.cs
@@ -222,10 +222,7 @@ internal unsafe class GameFunctions : IDisposable
}
catch (Exception e)
{
- // Static method has no instance _logger to reach. Promoting this to
- // an instance method would force PayloadHandler.cs:814 (the only
- // caller) onto Plugin.Functions.* indirection. Lighter touch for
- // DI-4 Slice B is to keep this one site on Plugin.LogProxy.
+ // Static method, no instance _logger reachable here.
Plugin.LogProxy.Warning(e, "Unable to open adventurer plate");
return false;
}
diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj
index 51f6e40..e2dd1cb 100644
--- a/HellionChat/HellionChat.csproj
+++ b/HellionChat/HellionChat.csproj
@@ -16,7 +16,10 @@
-
+
diff --git a/HellionChat/Infrastructure/Hosting/InitHostedServices.cs b/HellionChat/Infrastructure/Hosting/InitHostedServices.cs
index d7932e5..e43f482 100644
--- a/HellionChat/Infrastructure/Hosting/InitHostedServices.cs
+++ b/HellionChat/Infrastructure/Hosting/InitHostedServices.cs
@@ -5,13 +5,11 @@ using Microsoft.Extensions.Hosting;
namespace HellionChat.Infrastructure.Hosting;
-// Adapter shells around the IHostedService contract so the host can resolve
-// the underlying singletons eagerly and trigger their existing init methods
-// without modifying the service class bodies (DI-2a constraint). Bodies that
-// stay empty here still serve a purpose: the host resolves the service when
-// it instantiates the hosted service, which forces the ctor (IPC subscribe
-// for IpcManager / TypingIpc / ExtraChat) to run during StartAsync instead of
-// lazily on first GetRequiredService.
+// Adapter shells around IHostedService so the host triggers each service's
+// existing init method without touching the service class itself. Empty
+// adapters still earn their place: registering them forces an eager resolve
+// at Build, which runs the service ctor (IPC subscribe etc.) right then
+// instead of lazily on first GetRequiredService.
internal sealed class FontManagerInitHostedService(FontManager fontManager) : IHostedService
{
@@ -39,11 +37,8 @@ internal sealed class ThemeRegistryInitHostedService(ThemeRegistry registry) : I
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
-// IPC subscribers do their wiring in the ctor today, so StartAsync stays a
-// no-op — the value of registering them as hosted services is that the host
-// resolves them eagerly during Build, which triggers the ctor work. Moving
-// the body into StartAsync is a DI-2b follow-up after the service ctors are
-// allowed to change.
+// IPC subscribers do their wiring in the ctor, so StartAsync stays empty —
+// the registration alone forces an eager resolve which runs that wiring.
internal sealed class IpcManagerInitHostedService(IpcManager ipc) : IHostedService
{
diff --git a/HellionChat/Infrastructure/Logging/DalamudLogger.cs b/HellionChat/Infrastructure/Logging/DalamudLogger.cs
index 09375ff..0bb8462 100644
--- a/HellionChat/Infrastructure/Logging/DalamudLogger.cs
+++ b/HellionChat/Infrastructure/Logging/DalamudLogger.cs
@@ -33,11 +33,8 @@ internal sealed class DalamudLogger : ILogger
if (!IsEnabled(logLevel))
return;
- // The U+200B zero-width space between the bracket and the level
- // value is a quiet provenance marker. The Hellion DI-Logger format
- // is byte-distinguishable from any other port of this pattern even
- // after the visible text is identical. EUPL-1.2 reuse stays valid;
- // attribution traces stay possible.
+ // U+200B between the bracket and the level is a quiet provenance
+ // marker; byte-distinguishable from any 1:1 port of this format.
if ((int)logLevel <= (int)LogLevel.Information)
{
_pluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}");
diff --git a/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs b/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs
index befb215..ad948cb 100644
--- a/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs
+++ b/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs
@@ -10,9 +10,7 @@ namespace HellionChat.Infrastructure.Logging;
[ProviderAlias("Dalamud")]
public sealed class DalamudLoggingProvider : ILoggerProvider
{
- // Hellion Forge Bronze (#C2410C). Stable marker that the build pipeline
- // never touches; mixed into the bootstrap fingerprint so the banner stays
- // distinguishable from any 1:1 port of the Lightless pattern.
+ // Hellion Forge Bronze (#C2410C). Mixed into the bootstrap fingerprint.
private const string HellionMarker = "HellionForgeBronzeC2410C";
private readonly ConcurrentDictionary _loggers = new(
@@ -27,12 +25,8 @@ public sealed class DalamudLoggingProvider : ILoggerProvider
EmitBootstrapBanner();
}
- // Runs once per plugin load (the provider is a container singleton). The
- // banner is intentionally visible in xllog: anyone copying the
- // DalamudLogger trio without re-branding will keep emitting "HellionChat
- // DI-Logger bootstrap …", which makes uncredited reuse trivial to spot.
- // EUPL-1.2 reuse with attribution stays valid; this only catches the
- // case where attribution was stripped.
+ // One-shot per plugin load. Intentionally visible in xllog so uncredited
+ // ports of the DalamudLogger trio keep announcing their origin.
private void EmitBootstrapBanner()
{
var version =
diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs
index c374464..31ceff3 100755
--- a/HellionChat/Plugin.cs
+++ b/HellionChat/Plugin.cs
@@ -125,9 +125,8 @@ public sealed class Plugin : IAsyncDalamudPlugin
// isolation. Wired immediately after Dalamud injects Log.
internal static IPluginLogProxy LogProxy { get; private set; } = null!;
- // Container drives the v1.5.0 bootstrap. Both are nullable so DisposeAsync
- // stays safe if Phase-1 (Host build) throws before they get assigned -
- // Dalamud fires DisposeAsync regardless of how far the ctor got.
+ // Nullable so DisposeAsync stays safe if Host-build throws before the
+ // fields get assigned — Dalamud fires DisposeAsync regardless.
private readonly IHost? _host;
private readonly PluginLifecycle? _lifecycle;
@@ -222,17 +221,14 @@ public sealed class Plugin : IAsyncDalamudPlugin
DeferredSaveFrames = -1;
// Custom themes dir + seed run before the container builds so the
- // ThemeRegistry factory lambda finds the directory ready and the
- // example theme stays in place if the user has not touched it.
+ // ThemeRegistry factory lambda finds the directory ready.
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
Directory.CreateDirectory(customThemesDir);
SeedExampleThemeIfEmpty(customThemesDir);
- // Phase-1: build the generic host and pull singletons out into the
- // Plugin.X surface so consumers untouched by DI-2a keep working. The
- // host stays sync here because the schema gate above must run before
- // services allocate; deferring the build to LoadAsync (Lightless'
- // pattern) would mean the gate fires after the container is alive.
+ // Phase-1: build the host synchronously (the schema gate must clear
+ // before services allocate; Lightless' deferred build would invert
+ // that order) and pull singletons into the Plugin.X surface.
var dependencies = new PluginHostDependencies(
Interface,
Log,
@@ -527,10 +523,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
);
- // Framework-thread cleanup the container does not reach. TearDownCommands
- // walks Plugin-private dictionaries; SetChatInteractable is a static
- // call into game state; WindowSystem.RemoveAllWindows clears the
- // backing List<> that AddWindow populated in PluginLifecycle.LoadAsync.
+ // Framework-thread cleanup the container does not reach.
try
{
await Framework
@@ -550,12 +543,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
failure ??= ex;
}
- // Lifecycle stops the host (HostedService.StopAsync) and disposes the
- // container on the framework thread; that path disposes all the
- // services + windows we used to dispose manually here. The smoke from
- // C3 surfaced MessageManager.DisposeAsync as non-idempotent (CTS
- // dispose at line 99 throws on a second call), so we hand the entire
- // service teardown to the container instead of double-disposing.
+ // Container disposes services + windows on the framework thread.
+ // MessageManager.DisposeAsync is not idempotent, so we let the
+ // container do it once instead of double-disposing.
if (_lifecycle is not null)
{
failure = await CaptureFailureAsync(failure, () => _lifecycle.DisposeAsync().AsTask())
diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs
index 32d5199..c8781bc 100644
--- a/HellionChat/PluginHostFactory.cs
+++ b/HellionChat/PluginHostFactory.cs
@@ -39,11 +39,7 @@ internal static class PluginHostFactory
PluginHostDependencies dependencies
)
{
- // -----------------------------------------------------------------
- // Block A — Dalamud-Services (21 [PluginService] singletons, plus the
- // dependencies record itself). Registered by-interface so consumer
- // ctors can resolve them without touching Plugin statics.
- // -----------------------------------------------------------------
+ // Block A — Dalamud services (21 [PluginService] singletons).
services.AddSingleton(dependencies);
services.AddSingleton(dependencies.PluginInterface);
services.AddSingleton(dependencies.PluginLog);
@@ -67,31 +63,14 @@ internal static class PluginHostFactory
services.AddSingleton(dependencies.Evaluator);
services.AddSingleton(dependencies.SelfTestRegistry);
- // -----------------------------------------------------------------
- // Self-reference. Plugin owns the [PluginService] static surface and
- // is already constructed by Dalamud before this factory runs, so we
- // register the existing instance instead of letting the container
- // build one.
- // -----------------------------------------------------------------
+ // Self-references: Plugin and its WindowSystem already exist.
services.AddSingleton(plugin);
services.AddSingleton(plugin.WindowSystem);
-
- // PluginLifecycle is a thin orchestrator over IHost; Plugin.ctor pulls
- // it via GetRequiredService() immediately after Build.
services.AddSingleton();
- // -----------------------------------------------------------------
- // Block B — HellionChat singletons (14 + 1 FileDialogManager adapter).
- // Service-class bodies stay untouched in v1.5.0 per the DI-2a
- // constraint; ctors that need a Plugin backref go through a factory
- // lambda that resolves Plugin from the container.
- // -----------------------------------------------------------------
- // Factory lambdas across the board: Microsoft.Extensions.DependencyInjection's
- // ActivatorUtilities only inspects PUBLIC constructors via reflection,
- // and several HellionChat classes are `internal sealed` with implicit-
- // internal default ctors (Commands, StatusBar) or explicitly `internal`
- // ctors on public classes (ExtraChat). The lambda body compiles inside
- // the HellionChat namespace, so `new T()` sees the internal surface.
+ // Block B — HellionChat singletons. Factory lambdas because most
+ // classes are internal-sealed and the default activator only sees
+ // public ctors.
services.AddSingleton(_ => new DalamudPlatformUtil());
services.AddSingleton(sp => new DalamudPluginLogProxy(
sp.GetRequiredService()
@@ -133,10 +112,8 @@ internal static class PluginHostFactory
sp.GetRequiredService()
));
- // AutoTellTabsService pulls MessageStore through MessageManager.Store
- // because MessageStore is still allocated inside MessageManager.ctor
- // (DI-2a leaves that body untouched). Promoting MessageStore to its
- // own container singleton would double-construct the SQLite handle.
+ // MessageStore is allocated inside MessageManager.ctor; a separate
+ // container singleton would double-construct the SQLite handle.
services.AddSingleton(sp =>
{
var pluginRef = sp.GetRequiredService();
@@ -149,11 +126,8 @@ internal static class PluginHostFactory
);
});
- // -----------------------------------------------------------------
- // Block C — Windows (8, each takes Plugin or ChatLogWindow). The
- // host never AddWindow()s them; PluginLifecycle does that on the
- // framework thread once C3 wires it up (see plan §2 service order).
- // -----------------------------------------------------------------
+ // Block C — Windows. WindowSystem.AddWindow is called from
+ // PluginLifecycle.LoadAsync on the framework thread.
services.AddSingleton(sp => new ChatLogWindow(
sp.GetRequiredService(),
sp.GetRequiredService>(),
@@ -173,18 +147,8 @@ internal static class PluginHostFactory
services.AddSingleton(sp => new DebuggerWindow(sp.GetRequiredService()));
services.AddSingleton(sp => new FirstRunWizard(sp.GetRequiredService()));
- // -----------------------------------------------------------------
- // Hosted-service adapters — IHostedService is the host's only "after
- // bootstrap, before user interaction" hook, so we register thin
- // wrappers that call the existing init methods (BuildFonts, Switch,
- // FilterAllTabsAsync, Initialize) without modifying the service
- // bodies. Plan §2 documents why this is adapter-style instead of
- // making the services themselves implement IHostedService (Lightless'
- // pattern) — DI-2a leaves service classes untouched.
- // -----------------------------------------------------------------
- // Same internal-ctor pitfall as the singletons above - the adapter
- // classes are `internal sealed` with primary constructors, so the
- // direct AddHostedService() overload's ActivatorUtilities fails.
+ // Hosted-service adapters: thin wrappers around the existing init
+ // methods so the service class bodies stay unchanged.
services.AddHostedService(sp => new FontManagerInitHostedService(
sp.GetRequiredService()
));
diff --git a/HellionChat/PluginLifecycle.cs b/HellionChat/PluginLifecycle.cs
index 8497cbf..052fdf8 100644
--- a/HellionChat/PluginLifecycle.cs
+++ b/HellionChat/PluginLifecycle.cs
@@ -4,11 +4,9 @@ using Microsoft.Extensions.Hosting;
namespace HellionChat;
-// Orchestrates Host.StartAsync / StopAsync + dispose on the framework thread.
-// The Host itself is built sync in Plugin.ctor (before the schema gate clears)
-// and assigned via the property setter; PluginLifecycle never builds it
-// itself, which is why HellionChat skips Lightless' Func-delegate indirection
-// (see plan §9 risk "Bewusste Abweichung von Lightless").
+// Orchestrates Host.StartAsync / StopAsync and the framework-thread dispose.
+// Plugin.ctor builds the host and assigns it via the Host property, so
+// PluginLifecycle never constructs the host itself.
internal sealed class PluginLifecycle : IAsyncDisposable
{
private readonly IFramework _framework;
diff --git a/HellionChat/Util/IPluginLogProxy.cs b/HellionChat/Util/IPluginLogProxy.cs
index 40fd4bc..f3a7d35 100644
--- a/HellionChat/Util/IPluginLogProxy.cs
+++ b/HellionChat/Util/IPluginLogProxy.cs
@@ -2,10 +2,10 @@ using System;
namespace HellionChat.Util;
-// Indirection over Dalamud's IPluginLog so MessageStore can be constructed
-// in an isolated xUnit AppDomain without loading Dalamud.dll — same pattern
-// as IPlatformUtil from F12.1. A later DI-container cycle (v1.5.x) may
-// replace this with Microsoft.Extensions.Logging's ILogger.
+// Plugin.LogProxy bridge for consumers that cannot take a logger via the
+// constructor: static helpers (EmoteCache et al.), Dalamud-reflected types
+// (Configuration), data classes with mass instantiation (Message) and
+// instance classes that only log from static methods (FontManager).
internal interface IPluginLogProxy
{
void Verbose(string message);