diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs index f538536..86194b6 100644 --- a/HellionChat/Resources/HellionStrings.Designer.cs +++ b/HellionChat/Resources/HellionStrings.Designer.cs @@ -41,11 +41,9 @@ internal class HellionStrings private static string Get(string key) => ResourceManager.GetString(key, resourceCulture) ?? key; - internal static string Privacy_Tab_Title => Get(nameof(Privacy_Tab_Title)); internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name)); internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description)); internal static string Privacy_FilterEnabled_StorageOnly_Help => Get(nameof(Privacy_FilterEnabled_StorageOnly_Help)); - internal static string Privacy_Filter_Tree_Heading => Get(nameof(Privacy_Filter_Tree_Heading)); internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help)); internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst)); internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll)); @@ -260,8 +258,6 @@ internal class HellionStrings internal static string Settings_Card_Chat_Subtext => Get(nameof(Settings_Card_Chat_Subtext)); internal static string Settings_Card_Tabs_Title => Get(nameof(Settings_Card_Tabs_Title)); internal static string Settings_Card_Tabs_Subtext => Get(nameof(Settings_Card_Tabs_Subtext)); - internal static string Settings_Card_Privacy_Title => Get(nameof(Settings_Card_Privacy_Title)); - internal static string Settings_Card_Privacy_Subtext => Get(nameof(Settings_Card_Privacy_Subtext)); internal static string Settings_Card_Database_Title => Get(nameof(Settings_Card_Database_Title)); internal static string Settings_Card_Database_Subtext => Get(nameof(Settings_Card_Database_Subtext)); internal static string Settings_Card_Information_Title => Get(nameof(Settings_Card_Information_Title)); @@ -373,13 +369,16 @@ internal class HellionStrings internal static string Settings_ThemeAndLayout_WindowOpacity_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacity_Description)); // Hellion Chat — v1.2.1 Data Management tab section headings - internal static string Settings_DataManagement_Storage_Heading => Get(nameof(Settings_DataManagement_Storage_Heading)); - internal static string Settings_DataManagement_Retention_Heading => Get(nameof(Settings_DataManagement_Retention_Heading)); - internal static string Settings_DataManagement_Cleanup_Heading => Get(nameof(Settings_DataManagement_Cleanup_Heading)); - internal static string Settings_DataManagement_Export_Heading => Get(nameof(Settings_DataManagement_Export_Heading)); - internal static string Settings_DataManagement_DbViewer_Heading => Get(nameof(Settings_DataManagement_DbViewer_Heading)); internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading)); + // v1.5.6: Data & Privacy tab section titles (R6) + internal static string Settings_Section_PrivacyFilter => Get(nameof(Settings_Section_PrivacyFilter)); + internal static string Settings_Section_Storage => Get(nameof(Settings_Section_Storage)); + internal static string Settings_Section_Retention => Get(nameof(Settings_Section_Retention)); + internal static string Settings_Section_Cleanup => Get(nameof(Settings_Section_Cleanup)); + internal static string Settings_Section_Export => Get(nameof(Settings_Section_Export)); + internal static string Settings_Section_Database => Get(nameof(Settings_Section_Database)); + // Hellion Chat — v1.2.1 Migration v15 → v16 toast internal static string Migration_v16_OverrideStyle_Toast => Get(nameof(Migration_v16_OverrideStyle_Toast)); diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx index f8c30ce..48e39df 100644 --- a/HellionChat/Resources/HellionStrings.resx +++ b/HellionChat/Resources/HellionStrings.resx @@ -12,9 +12,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Privacy - Enable privacy filter @@ -24,9 +21,6 @@ The filter only controls what is written to the local database. The chat log still shows every message live; excluded channels are simply no longer stored. If you also want to remove channels from the visible display, use the normal chat-tab filters in the game. - - Privacy filter and whitelist - Choose which channels are saved to the local database. Default follows data minimisation: only your own conversations. Use the buttons below to apply a preset. @@ -773,12 +767,6 @@ Create and configure custom chat tabs. - - Privacy - - - Privacy filter per channel and what may be stored. - Database @@ -857,20 +845,23 @@ How transparent the window background is. Lower values let more of the game show through. Tip: Dalamud's per-window menu (hamburger in the title bar) offers per-window overrides for opacity, background blur, click-through, and pinning. Those take precedence over this slider for the respective window. - + + Privacy filter + + Storage - + Retention - + Cleanup - + Export - - Database viewer + + Database Advanced (Shift+click to open) diff --git a/HellionChat/Ui/FirstRunWizard.cs b/HellionChat/Ui/FirstRunWizard.cs index 45f378b..568a42d 100644 --- a/HellionChat/Ui/FirstRunWizard.cs +++ b/HellionChat/Ui/FirstRunWizard.cs @@ -368,7 +368,7 @@ public sealed class FirstRunWizard : Window // Mirror the DataAndPrivacy coupling: turning load-previous on // also turns filter-include on (otherwise old messages bypass // the filter chain), and turning filter-include off forces - // load-previous off. Same idiom as Ui/SettingsTabs/DataAndPrivacy.cs:182-200. + // load-previous off. Same idiom as Ui/SettingsTabs/DataAndPrivacy.cs. if (loadPrev) _state.PendingFilterIncludePreviousSessions = true; } diff --git a/HellionChat/Ui/Settings.cs b/HellionChat/Ui/Settings.cs index dd2ad5c..4dd4370 100755 --- a/HellionChat/Ui/Settings.cs +++ b/HellionChat/Ui/Settings.cs @@ -54,7 +54,6 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window new SettingsTabs.Window(Plugin, Mutable), new Chat(Plugin, Mutable), new SettingsTabs.Tabs(Plugin, Mutable), - new SettingsTabs.Privacy(Plugin, Mutable), new DataAndPrivacy(Plugin, Mutable, loggerFactory.CreateLogger()), new SettingsTabs.Integrations(Plugin, Mutable), new About(Mutable), diff --git a/HellionChat/Ui/SettingsOverview.cs b/HellionChat/Ui/SettingsOverview.cs index ad633bb..c14cf33 100644 --- a/HellionChat/Ui/SettingsOverview.cs +++ b/HellionChat/Ui/SettingsOverview.cs @@ -39,11 +39,6 @@ internal sealed class SettingsOverview HellionStrings.Settings_Card_Tabs_Title, HellionStrings.Settings_Card_Tabs_Subtext ), - ( - FontAwesomeIcon.ShieldAlt, - HellionStrings.Settings_Card_Privacy_Title, - HellionStrings.Settings_Card_Privacy_Subtext - ), ( FontAwesomeIcon.Database, HellionStrings.Settings_Card_DataManagement_Title, diff --git a/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs b/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs index 4be8c5a..e96609b 100644 --- a/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs +++ b/HellionChat/Ui/SettingsTabs/DataAndPrivacy.cs @@ -24,7 +24,7 @@ internal sealed class DataAndPrivacy : ISettingsTab public string Name => HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement"; - // Cleanup state (was in Privacy.cs) + // Cleanup state private Dictionary? CleanupCounts; private long CleanupKeepCount; private long CleanupDeleteCount; @@ -33,7 +33,7 @@ internal sealed class DataAndPrivacy : ISettingsTab private HashSet? CleanupPreviewSnapshot; private bool RetentionRunning => Plugin.RetentionSweepRunning; - // Export form state (was in Privacy.cs) + // Export form state private int ExportRangeDays = 30; private string ExportSenderSubstring = string.Empty; private readonly HashSet ExportSelectedChannels = []; @@ -152,22 +152,113 @@ internal sealed class DataAndPrivacy : ISettingsTab if (sectionJustEntered) ShowAdvanced = ImGui.GetIO().KeyShift; + if (sectionJustEntered) ImGui.SetNextItemOpen(false); + DrawPrivacyFilterSection(); + ImGui.Spacing(); + if (sectionJustEntered) ImGui.SetNextItemOpen(false); DrawStorageSection(); ImGui.Spacing(); + if (sectionJustEntered) ImGui.SetNextItemOpen(false); DrawRetentionSection(); ImGui.Spacing(); + if (sectionJustEntered) ImGui.SetNextItemOpen(false); DrawCleanupSection(); ImGui.Spacing(); + if (sectionJustEntered) ImGui.SetNextItemOpen(false); DrawExportSection(); ImGui.Spacing(); - DrawDatabaseViewerSection(); - ImGui.Spacing(); - DrawAdvancedSection(); + if (sectionJustEntered) ImGui.SetNextItemOpen(false); + DrawDatabaseSection(); + } + + private void DrawPrivacyFilterSection() + { + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_PrivacyFilter); + if (!tree.Success) + return; + + using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) + { + // Wizard re-open sits outside the disabled block so it is always clickable. + if (ImGui.Button(HellionStrings.Wizard_Reopen_Button)) + Plugin.FirstRunWizard.IsOpen = true; + ImGui.Spacing(); + + ImGuiUtil.OptionCheckbox( + ref Mutable.PrivacyFilterEnabled, + HellionStrings.Privacy_FilterEnabled_Name, + HellionStrings.Privacy_FilterEnabled_Description + ); + ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help); + + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + + // Whitelist, presets, and PersistUnknown are greyed (still visible) + // when the filter is off — ImRaii.Disabled block preserved verbatim. + using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled)) + { + ImGuiUtil.HelpText(HellionStrings.Privacy_Whitelist_Help); + + ImGui.Spacing(); + + if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst)) + Mutable.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist]; + + ImGui.SameLine(); + if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll)) + Mutable.PrivacyPersistChannels.Clear(); + + ImGui.SameLine(); + if (ImGui.Button(HellionStrings.Privacy_Preset_SelectAll)) + foreach (var group in Groups) + foreach (var t in group.Types) + Mutable.PrivacyPersistChannels.Add(t); + + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + + foreach (var (heading, types) in Groups) + { + using var groupTree = ImRaii.TreeNode(heading()); + if (!groupTree.Success) + continue; + + using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) + { + foreach (var type in types) + { + var enabled = Mutable.PrivacyPersistChannels.Contains(type); + var label = type.ToString(); + if (ImGui.Checkbox($"{label}##privacy-{(int)type}", ref enabled)) + { + if (enabled) + Mutable.PrivacyPersistChannels.Add(type); + else + Mutable.PrivacyPersistChannels.Remove(type); + } + } + } + } + + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + + ImGuiUtil.OptionCheckbox( + ref Mutable.PrivacyPersistUnknownChannels, + HellionStrings.Privacy_PersistUnknown_Name, + HellionStrings.Privacy_PersistUnknown_Description + ); + } + } } private void DrawStorageSection() { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Storage_Heading); + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Storage); if (!tree.Success) return; @@ -245,7 +336,7 @@ internal sealed class DataAndPrivacy : ISettingsTab private void DrawRetentionSection() { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Retention_Heading); + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Retention); if (!tree.Success) return; @@ -437,7 +528,7 @@ internal sealed class DataAndPrivacy : ISettingsTab private void DrawCleanupSection() { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Cleanup_Heading); + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Cleanup); if (!tree.Success) return; @@ -627,7 +718,7 @@ internal sealed class DataAndPrivacy : ISettingsTab private void DrawExportSection() { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Export_Heading); + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Export); if (!tree.Success) return; @@ -785,9 +876,9 @@ internal sealed class DataAndPrivacy : ISettingsTab }.Start(); } - private void DrawDatabaseViewerSection() + private void DrawDatabaseSection() { - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_DbViewer_Heading); + using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Database); if (!tree.Success) return; @@ -862,49 +953,49 @@ internal sealed class DataAndPrivacy : ISettingsTab NotificationType.Info ); } - } - } - private void DrawAdvancedSection() - { - if (!ShowAdvanced) - return; + // Advanced sub-block: only visible when the tab was opened with Shift held. + // Gate matches the Shift-on-open flag set at the top of Draw(). + if (!ShowAdvanced) + return; - using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Advanced_Heading); - if (!tree.Success) - return; + ImGui.Spacing(); + using var advTree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Advanced_Heading); + if (!advTree.Success) + return; - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - using var wrap = ImRaii.TextWrapPos(0.0f); - - ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning); - if ( - ImGuiUtil.CtrlShiftButton( - "Perform maintenance", - "Ctrl+Shift: MessageManager.Store.PerformMaintenance()" - ) - ) - Plugin.MessageManager.Store.PerformMaintenance(); - - if ( - ImGuiUtil.CtrlShiftButton( - "Reload messages from database", - "Ctrl+Shift: MessageManager.FilterAllTabs()" - ) - ) + using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) { - Plugin.MessageManager.ClearAllTabs(); - Plugin.MessageManager.FilterAllTabsAsync(); - } + using var wrap = ImRaii.TextWrapPos(0.0f); - if ( - ImGuiUtil.CtrlShiftButton( - "Inject 10,000 messages", - "Ctrl+Shift: creates 10,000 unique messages (async)" + ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning); + if ( + ImGuiUtil.CtrlShiftButton( + "Perform maintenance", + "Ctrl+Shift: MessageManager.Store.PerformMaintenance()" + ) ) - ) - new Thread(() => InsertMessages(10_000)).Start(); + Plugin.MessageManager.Store.PerformMaintenance(); + + if ( + ImGuiUtil.CtrlShiftButton( + "Reload messages from database", + "Ctrl+Shift: MessageManager.FilterAllTabs()" + ) + ) + { + Plugin.MessageManager.ClearAllTabs(); + Plugin.MessageManager.FilterAllTabsAsync(); + } + + if ( + ImGuiUtil.CtrlShiftButton( + "Inject 10,000 messages", + "Ctrl+Shift: creates 10,000 unique messages (async)" + ) + ) + new Thread(() => InsertMessages(10_000)).Start(); + } } } diff --git a/HellionChat/Ui/SettingsTabs/Privacy.cs b/HellionChat/Ui/SettingsTabs/Privacy.cs deleted file mode 100644 index 0bb5082..0000000 --- a/HellionChat/Ui/SettingsTabs/Privacy.cs +++ /dev/null @@ -1,198 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Utility.Raii; -using HellionChat.Code; -using HellionChat.Privacy; -using HellionChat.Resources; -using HellionChat.Util; - -namespace HellionChat.Ui.SettingsTabs; - -internal sealed class Privacy : ISettingsTab -{ - private Plugin Plugin { get; } - private Configuration Mutable { get; } - - public string Name => HellionStrings.Privacy_Tab_Title + "###tabs-privacy"; - - internal Privacy(Plugin plugin, Configuration mutable) - { - Plugin = plugin; - Mutable = mutable; - } - - // (HeadingKey, ChatType list). Heading resolved per-frame for live language switching. - private static readonly (Func Heading, ChatType[] Types)[] Groups = - [ - ( - () => HellionStrings.Privacy_Group_DirectMessages, - [ChatType.TellIncoming, ChatType.TellOutgoing] - ), - ( - () => HellionStrings.Privacy_Group_PartyAlliance, - [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam] - ), - ( - () => HellionStrings.Privacy_Group_FreeCompany, - [ - ChatType.FreeCompany, - ChatType.FreeCompanyAnnouncement, - ChatType.FreeCompanyLoginLogout, - ] - ), - ( - () => HellionStrings.Privacy_Group_Linkshells, - [ - ChatType.Linkshell1, - ChatType.Linkshell2, - ChatType.Linkshell3, - ChatType.Linkshell4, - ChatType.Linkshell5, - ChatType.Linkshell6, - ChatType.Linkshell7, - ChatType.Linkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_CrossLinkshells, - [ - ChatType.CrossLinkshell1, - ChatType.CrossLinkshell2, - ChatType.CrossLinkshell3, - ChatType.CrossLinkshell4, - ChatType.CrossLinkshell5, - ChatType.CrossLinkshell6, - ChatType.CrossLinkshell7, - ChatType.CrossLinkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_ExtraChat, - [ - ChatType.ExtraChatLinkshell1, - ChatType.ExtraChatLinkshell2, - ChatType.ExtraChatLinkshell3, - ChatType.ExtraChatLinkshell4, - ChatType.ExtraChatLinkshell5, - ChatType.ExtraChatLinkshell6, - ChatType.ExtraChatLinkshell7, - ChatType.ExtraChatLinkshell8, - ] - ), - ( - () => HellionStrings.Privacy_Group_PublicChat, - [ - ChatType.Say, - ChatType.Shout, - ChatType.Yell, - ChatType.NoviceNetwork, - ChatType.CustomEmote, - ChatType.StandardEmote, - ] - ), - ( - () => HellionStrings.Privacy_Group_SystemLogs, - [ - ChatType.System, - ChatType.Notice, - ChatType.Urgent, - ChatType.Echo, - ChatType.NpcDialogue, - ChatType.NpcAnnouncement, - ChatType.LootNotice, - ChatType.LootRoll, - ChatType.RetainerSale, - ChatType.Crafting, - ChatType.Gathering, - ChatType.Sign, - ChatType.RandomNumber, - ] - ), - ]; - - public void Draw(bool sectionJustEntered) - { - if (ImGui.Button(HellionStrings.Wizard_Reopen_Button)) - Plugin.FirstRunWizard.IsOpen = true; - ImGui.Spacing(); - - DrawPrivacyFilterSection(); - } - - private void DrawPrivacyFilterSection() - { - using var tree = ImRaii.TreeNode(HellionStrings.Privacy_Filter_Tree_Heading); - if (!tree.Success) - return; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - ImGuiUtil.OptionCheckbox( - ref Mutable.PrivacyFilterEnabled, - HellionStrings.Privacy_FilterEnabled_Name, - HellionStrings.Privacy_FilterEnabled_Description - ); - ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled)) - { - ImGuiUtil.HelpText(HellionStrings.Privacy_Whitelist_Help); - - ImGui.Spacing(); - - if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst)) - Mutable.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist]; - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll)) - Mutable.PrivacyPersistChannels.Clear(); - - ImGui.SameLine(); - if (ImGui.Button(HellionStrings.Privacy_Preset_SelectAll)) - foreach (var group in Groups) - foreach (var t in group.Types) - Mutable.PrivacyPersistChannels.Add(t); - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - foreach (var (heading, types) in Groups) - { - using var groupTree = ImRaii.TreeNode(heading()); - if (!groupTree.Success) - continue; - - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - { - foreach (var type in types) - { - var enabled = Mutable.PrivacyPersistChannels.Contains(type); - var label = type.ToString(); - if (ImGui.Checkbox($"{label}##privacy-{(int)type}", ref enabled)) - { - if (enabled) - Mutable.PrivacyPersistChannels.Add(type); - else - Mutable.PrivacyPersistChannels.Remove(type); - } - } - } - } - - ImGui.Spacing(); - ImGui.Separator(); - ImGui.Spacing(); - - ImGuiUtil.OptionCheckbox( - ref Mutable.PrivacyPersistUnknownChannels, - HellionStrings.Privacy_PersistUnknown_Name, - HellionStrings.Privacy_PersistUnknown_Description - ); - } - } - } -} diff --git a/README.md b/README.md index 9f9f12c..bb2c0c8 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ HellionChat/ │ ├── FirstRunWizard.cs # Three-profile onboarding │ ├── HellionStyle.cs # ImGui theme push (local and global) │ └── SettingsTabs/ -│ └── Privacy.cs # Privacy tab (filters, retention, cleanup, export) +│ └── DataAndPrivacy.cs # Data & Privacy tab (filters, retention, cleanup, export) ├── Ipc/ # IPC channels, migrated to HellionChat.* in v1.0.0 ├── ChatTwoConflictDetector.cs # Blocks plugin load if upstream Chat 2 is active ├── images/ diff --git a/docs/AI_DISCLOSURE.md b/docs/AI_DISCLOSURE.md index ea2ccad..cc25d7f 100644 --- a/docs/AI_DISCLOSURE.md +++ b/docs/AI_DISCLOSURE.md @@ -45,7 +45,7 @@ plugin development in general. Upstream Chat 2 (by Infi & Anna, EUPL-1.2) is the foundation and was not produced with AI assistance. HellionChat-specific code lives in `HellionChat/Privacy/`, `HellionChat/Export/`, -`HellionChat/Resources/HellionStrings*`, `Ui/SettingsTabs/Privacy.cs`, `Ui/FirstRunWizard.cs`, +`HellionChat/Resources/HellionStrings*`, `Ui/SettingsTabs/DataAndPrivacy.cs`, `Ui/FirstRunWizard.cs`, `Ui/HellionStyle.cs`, plus the Migrate3 recovery and plugin layout migration in `MessageStore.cs` and `Plugin.cs`. These were developed with Pair-level assistance as described above.