chore: comments, second pass -- the task codes the first pass missed

The first sweep matched a character class that swallowed the digit, so a bare
B1 slipped through while B1-2 was caught. Searching the whole A-Z space instead
of guessing prefixes turned up 130-odd more: B0 through B6, C2, C3, D1, H2, M6,
P7, P8, T2, W2, plus GP-04, KB-01, OD-1, PM-1, PM-3, SEC-01, TR-4, TR-7, UI-11,
UI-12, XC-8 and API-3.

Kept deliberately: 41 B4 01 is a byte signature, "N0" a format string,
#L119-L128 a source anchor, LS4/LS6 are linkshells, and A=FF B=0C G=41 R=C2
explains a colour-channel order. Those look like codes and are not.

Also translated the eight German comments left in the theme files and
ImGuiUtil. Seven of them described what a palette does to which channel, which
is worth reading -- just not in a second language in an otherwise English
codebase.
This commit is contained in:
2026-08-19 22:03:12 +02:00
parent 5b738e6885
commit 16557213cd
59 changed files with 262 additions and 193 deletions
+134 -67
View File
@@ -134,7 +134,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
// Ctor-smoke anchors (B0-2). Exposed so the Payload/Chunk ctor-smoke steps
// Ctor-smoke anchors. Exposed so the Payload/Chunk ctor-smoke steps
// can drive the real per-frame Lender path (Borrow()) and the eager
// singletons through the container, never via new(). Mirror of the
// FontManager property pattern — every SelfTest reaches services this way.
@@ -178,7 +178,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal bool ChatActivationRequested;
// Set in the first DisposeAsync statement so async callbacks scheduled
// via Framework.RunOnTick (v1.4.8 B3 retention sweep) can early-bail
// via Framework.RunOnTick (v1.4.8 retention sweep) can early-bail
// before they touch state that has already been torn down. Volatile
// because the tick reads it from a different thread than the writer.
private volatile bool _isDisposing;
@@ -188,7 +188,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// just unloaded belongs to nobody.
internal bool IsDisposing => _isDisposing;
// v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at
// v1.9.0: last full Draw() wall-time in ms, written once per frame at
// the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push
// and the font push (the first-frame hitch measurement must include atlas/style
// prologue cost), not just WindowSystem.Draw — measuring the inner call
@@ -214,7 +214,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// must never block doing so.
internal readonly Util.DbOperationGate DbOperations = new();
// B3: neutral owner of the Config.Tabs LIST-structure lock so both the
// Neutral owner of the Config.Tabs LIST-structure lock so both the
// worker-thread mutator (AutoTellTabsService) and the framework-thread
// refilter (MessageManager) share ONE monitor. Lock order: this outer,
// MessageList's SemaphoreSlim inner — never the reverse.
@@ -287,83 +287,118 @@ public sealed class Plugin : IAsyncDalamudPlugin
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
);
}
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
// set it false (only effective in 1.5.6) wanted top tabs — carry that
// intent forward. Runs only for pre-v23 configs; fresh configs load at
// LatestVersion and skip it. Additive v20/v22 fields keep their
// initializer defaults as before.
if (Config.Version < 23 && !Config.SidebarTabView)
// 2.0.0 does not migrate, it starts over. Five cycles rebuilt the whole
// window layer, and a config carried through them keeps values chosen
// against surfaces that no longer exist -- an opacity picked for a
// window that has been redrawn twice since, tabs laid out for a sidebar
// that works differently now. Every user of this build is a tester who
// was told this happens, and it is the only way to be sure everyone
// sees the same defaults.
//
// The file is copied aside first. Rolling back to 1.5.6 is a supported
// move here and it stays cheap: the old settings are a file copy away,
// rather than an evening of clicking them back in.
if (Config.Version < 27)
{
Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs;
}
BackUpConfigBeforeReset();
Config = Configuration.CreateFresh();
Config.Version = 27;
// Saved immediately: a crash between here and the first user-driven
// save would otherwise run the whole reset again on the next start,
// and the second run would back up the already-reset file over the
// real backup.
SaveConfig();
// v24 migration: the privacy filter used to route a known but unticked
// channel through the unknown-type failsafe, so the channel grid was
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
// that never picked a channel was storing everything through that hole,
// and the corrected rule would store nothing at all -- so the intent is
// carried forward as a filter that is honestly switched off.
if (
Config.Version < 24
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
Config.PrivacyFilterEnabled,
Config.PrivacyPersistUnknownChannels,
Config.PrivacyPersistChannels.Count
)
)
{
Config.PrivacyFilterEnabled = false;
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
// resolved from the container further down.
Log.Information(
"Privacy filter switched off during the v24 migration: it was on with no channels "
+ "picked, which stored everything through the unknown-channel failsafe. Pick "
+ "channels in Settings to switch it back on."
"Config reset to defaults for 2.0.0. Previous settings kept as "
+ "HellionChat.json.pre-2.0.0.bak next to the config file."
);
}
// v25 carried no migration step; the bump was documentation.
//
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
// whether a tab name is a person, and a config written before it existed
// has it false on every tab -- including pinned tell tabs, which survive
// reloads and are named "Player@World". Anything still carrying a tell
// binding or the temp flag got its name from a partner, so the flag is
// set from those two.
//
// Tabs promoted before this version are past saving: promotion clears
// both markers and keeps the name, so nothing in the stored data says
// where that name came from. Renaming one clears the flag anyway, which
// is the same outcome the user gets by editing it.
if (Config.Version < 26)
else
{
var carried = 0;
foreach (var tab in Config.Tabs)
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
// set it false (only effective in 1.5.6) wanted top tabs — carry that
// intent forward. Runs only for pre-v23 configs; fresh configs load at
// LatestVersion and skip it. Additive v20/v22 fields keep their
// initializer defaults as before.
if (Config.Version < 23 && !Config.SidebarTabView)
{
if (tab.NameCameFromPartner || (!tab.IsTempTab && tab.TellTarget?.IsSet() != true))
continue;
tab.NameCameFromPartner = true;
carried++;
Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs;
}
if (carried > 0)
// v24 migration: the privacy filter used to route a known but unticked
// channel through the unknown-type failsafe, so the channel grid was
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
// that never picked a channel was storing everything through that hole,
// and the corrected rule would store nothing at all -- so the intent is
// carried forward as a filter that is honestly switched off.
if (
Config.Version < 24
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
Config.PrivacyFilterEnabled,
Config.PrivacyPersistUnknownChannels,
Config.PrivacyPersistChannels.Count
)
)
{
Config.PrivacyFilterEnabled = false;
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
// resolved from the container further down.
Log.Information(
$"Marked {carried} tab(s) as partner-named during the v26 migration, so "
+ "screenshot mode hides them in the channel header."
"Privacy filter switched off during the v24 migration: it was on with no channels "
+ "picked, which stored everything through the unknown-channel failsafe. Pick "
+ "channels in Settings to switch it back on."
);
}
// v25 carried no migration step; the bump was documentation.
//
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
// whether a tab name is a person, and a config written before it existed
// has it false on every tab -- including pinned tell tabs, which survive
// reloads and are named "Player@World". Anything still carrying a tell
// binding or the temp flag got its name from a partner, so the flag is
// set from those two.
//
// Tabs promoted before this version are past saving: promotion clears
// both markers and keeps the name, so nothing in the stored data says
// where that name came from. Renaming one clears the flag anyway, which
// is the same outcome the user gets by editing it.
if (Config.Version < 26)
{
var carried = 0;
foreach (var tab in Config.Tabs)
{
if (
tab.NameCameFromPartner
|| (!tab.IsTempTab && tab.TellTarget?.IsSet() != true)
)
continue;
tab.NameCameFromPartner = true;
carried++;
}
if (carried > 0)
{
Log.Information(
$"Marked {carried} tab(s) as partner-named during the v26 migration, so "
+ "screenshot mode hides them in the channel header."
);
}
}
}
Config.Version = 26;
Config.Version = 27;
// Unpinned TempTabs are session-only and dropped on every load. Pinned
// TempTabs survive reload -- tester feedback in v1.4.7.
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
// GP-04: clear stale Tab.PopOut flags now — the pool binds further down
// Clear stale Tab.PopOut flags now — the pool binds further down
// (ChannelPopoutPool resolve below), so at this point no tab can own a
// slot. A persisted PopOut=true (notably on surviving pinned TempTabs)
// would otherwise be a flag with no window. Runs after the strip, before
@@ -462,7 +497,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>();
// Ctor-smoke anchors (B0-2). Resolved last, against the fully built
// Ctor-smoke anchors. Resolved last, against the fully built
// container: every MakePayloadHandler dep (MainWindow, InputBar,
// ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve
// below just reuses the same cached singleton. These are plain
@@ -732,7 +767,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
return;
// Set before any cleanup so deferred Framework.RunOnTick callbacks
// (B3 retention sweep) see the flag and bail out before they touch
// (the retention sweep) see the flag and bail out before they touch
// MessageManager / Log / static fields that the rest of this method
// is about to tear down.
_isDisposing = true;
@@ -840,6 +875,38 @@ public sealed class Plugin : IAsyncDalamudPlugin
return failure;
}
// Copies the config aside before the 2.0.0 reset overwrites it. Best effort
// by design: a backup that cannot be written must not stop the plugin from
// starting, and the reset itself is what the user was told would happen.
//
// Overwrite=false, so a second run cannot bury the real backup under a copy
// of the already-reset file. The reset saves immediately for the same
// reason, but a crash in between is exactly when this matters.
private static void BackUpConfigBeforeReset()
{
try
{
var dir = Interface.ConfigDirectory.Parent?.FullName;
if (dir is null)
return;
var configFile = Path.Combine(dir, "HellionChat.json");
if (!File.Exists(configFile))
return;
var backup = Path.Combine(dir, "HellionChat.json.pre-2.0.0.bak");
if (File.Exists(backup))
return;
File.Copy(configFile, backup);
Log.Information($"HellionChat: config backed up to {backup} before the 2.0.0 reset");
}
catch (Exception e)
{
Log.Warning(e, "HellionChat: could not back up the config before the 2.0.0 reset");
}
}
private static void MigrateFromChatTwoLayout()
{
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
@@ -1181,7 +1248,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Schedule on the next framework tick to avoid the ~194ms
// hitch from blocking with .Wait() while the frame finishes.
// The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is
// now guarded by the shared Plugin.TabsListLock (B3), so this
// now guarded by the shared Plugin.TabsListLock, so this
// tick scheduling is purely hitch-avoidance, not safety.
// Pattern reference: SimpleTweaks
// Tweaks/Chat/CaseInsensitiveCommands.cs:45.
@@ -1268,7 +1335,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
private void Draw()
{
// v1.9.0 B5: time the whole handler (style + font prologue included).
// v1.9.0: time the whole handler (style + font prologue included).
// Bail before measuring once teardown has begun — a late Draw tick
// must not touch ThemeRegistry / FontManager after DisposeAsync.
if (_isDisposing)
@@ -1277,7 +1344,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
var drawWatch = Stopwatch.StartNew();
try
{
// v1.4.8 B2: pick up external edits of the active custom theme JSON
// v1.4.8: pick up external edits of the active custom theme JSON
// without forcing the user to re-click the picker. The disk-stat is
// 1Hz-throttled inside RefreshActiveIfStale, so this is essentially
// free on built-in themes and ~1 stat/second on custom themes.
@@ -1367,7 +1434,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Config.Tabs across the save so JSON includes them. Cloning only the
// unpinned subset keeps the allocation proportional to
// AutoTellTabsLimit (<=15) instead of the full tab list.
// B3: the strip/restore mutates the tab LIST, so it shares TabsListLock
// The strip/restore mutates the tab LIST, so it shares TabsListLock
// with the worker add/remove and the refilter snapshot. Re-entrant: the
// one worker caller (HandleTell) already holds it; framework callers take
// it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback