From fbbbeebade745948d7d283c9597c4f74a0b6a843 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Fri, 15 May 2026 20:18:41 +0200
Subject: [PATCH 1/8] refactor(commands): cache slash-command wrappers in
private fields
TearDownCommands attached the same instance via re-Register with identical
args, which was functionally a no-op but masked a latent bug if Description
or ShowInHelp ever diverged between Setup and Teardown. Hold the wrapper
instances as nullable fields so Teardown can detach the live registration
directly. Mirrors the cached-wrapper pattern in ChatLogWindow.
---
HellionChat/Plugin.cs | 76 ++++++++++++++++++++++++++++---------------
1 file changed, 50 insertions(+), 26 deletions(-)
diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs
index 6c9301e..d3aeb88 100755
--- a/HellionChat/Plugin.cs
+++ b/HellionChat/Plugin.cs
@@ -123,6 +123,15 @@ public sealed class Plugin : IAsyncDalamudPlugin
// isolation. Wired immediately after Dalamud injects Log.
internal static IPluginLogProxy LogProxy { get; private set; } = null!;
+ // Wrapper cached so TearDown can detach the live instance instead of
+ // re-registering with identical args (v1.4.9 ISSUE-1 cleanup).
+ private CommandWrapper? _hellionSettingsCmd;
+ private CommandWrapper? _hellionViewCmd;
+ private CommandWrapper? _hellionDebuggerCmd;
+#if DEBUG
+ private CommandWrapper? _hellionSeStringCmd;
+#endif
+
// Idempotency guard — Dalamud may fire DisposeAsync twice in a reload race.
private int _disposeStarted;
@@ -699,21 +708,25 @@ public sealed class Plugin : IAsyncDalamudPlugin
{
// ChatLogWindow.cs:128 already registers /hellion (ToggleChat). The
// description-arg here keeps the Dalamud help list populated.
- Commands.Register("/hellion", "Perform various actions with Hellion Chat.").Execute +=
- OnHellionSettingsCommand;
- Commands
- .Register(
- "/hellionView",
- "Get access to your message history, with simple filter options.",
- true
- )
- .Execute += OnHellionViewCommand;
- Commands.Register("/hellionDebugger", showInHelp: false).Execute +=
- OnHellionDebuggerCommand;
+ _hellionSettingsCmd = Commands.Register(
+ "/hellion",
+ "Perform various actions with Hellion Chat."
+ );
+ _hellionSettingsCmd.Execute += OnHellionSettingsCommand;
+
+ _hellionViewCmd = Commands.Register(
+ "/hellionView",
+ "Get access to your message history, with simple filter options.",
+ true
+ );
+ _hellionViewCmd.Execute += OnHellionViewCommand;
+
+ _hellionDebuggerCmd = Commands.Register("/hellionDebugger", showInHelp: false);
+ _hellionDebuggerCmd.Execute += OnHellionDebuggerCommand;
#if DEBUG
// SeStringDebugger.cs lives under #if DEBUG too; keep this out of release builds.
- Commands.Register("/hellionSeString", showInHelp: false).Execute +=
- OnHellionSeStringCommand;
+ _hellionSeStringCmd = Commands.Register("/hellionSeString", showInHelp: false);
+ _hellionSeStringCmd.Execute += OnHellionSeStringCommand;
#endif
// Plugin-Manager "Settings" button. Was in Settings.cs:67 pre-v1.4.9.
@@ -729,20 +742,31 @@ public sealed class Plugin : IAsyncDalamudPlugin
Interface.UiBuilder.OpenMainUi -= OnOpenMainUi;
Interface.UiBuilder.OpenConfigUi -= OnOpenConfigUi;
- Commands.Register("/hellion", "Perform various actions with Hellion Chat.").Execute -=
- OnHellionSettingsCommand;
- Commands
- .Register(
- "/hellionView",
- "Get access to your message history, with simple filter options.",
- true
- )
- .Execute -= OnHellionViewCommand;
- Commands.Register("/hellionDebugger", showInHelp: false).Execute -=
- OnHellionDebuggerCommand;
+ // Null-tolerant detaches: TearDownCommands can run from the LoadAsync
+ // failure path (Plugin.cs CaptureFailure) before SetupCommands finished.
+ if (_hellionSettingsCmd is not null)
+ {
+ _hellionSettingsCmd.Execute -= OnHellionSettingsCommand;
+ _hellionSettingsCmd = null;
+ }
+
+ if (_hellionViewCmd is not null)
+ {
+ _hellionViewCmd.Execute -= OnHellionViewCommand;
+ _hellionViewCmd = null;
+ }
+
+ if (_hellionDebuggerCmd is not null)
+ {
+ _hellionDebuggerCmd.Execute -= OnHellionDebuggerCommand;
+ _hellionDebuggerCmd = null;
+ }
#if DEBUG
- Commands.Register("/hellionSeString", showInHelp: false).Execute -=
- OnHellionSeStringCommand;
+ if (_hellionSeStringCmd is not null)
+ {
+ _hellionSeStringCmd.Execute -= OnHellionSeStringCommand;
+ _hellionSeStringCmd = null;
+ }
#endif
}
From abbbf95002fde2a44d8f72afce760e8f8bcf7e35 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 01:11:12 +0200
Subject: [PATCH 2/8] feat(ui): add SymbolPicker popup with FFXIV icon tab
New popup attached to the chat input lets the user browse and insert
Dalamud SeIconChar glyphs (161 PUA codepoints, server-safe by design).
Search field filters by enum name. Multi-insert keeps the popup open
until the user clicks elsewhere. BMP tab follows in the next commit.
---
HellionChat/Ui/ChatLogWindow.cs | 27 +++++++
HellionChat/Ui/SymbolPicker.cs | 124 ++++++++++++++++++++++++++++++++
2 files changed, 151 insertions(+)
create mode 100644 HellionChat/Ui/SymbolPicker.cs
diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs
index a240da9..6710716 100644
--- a/HellionChat/Ui/ChatLogWindow.cs
+++ b/HellionChat/Ui/ChatLogWindow.cs
@@ -40,6 +40,7 @@ public sealed class ChatLogWindow : Window
private readonly CommandWrapper _clearHellionCommand;
private readonly CommandWrapper _hellionCommand;
+ private readonly SymbolPicker _symbolPicker;
internal bool ScreenshotMode;
private string Salt { get; }
@@ -129,6 +130,8 @@ public sealed class ChatLogWindow : Window
_clearHellionCommand.Execute += ClearLog;
_hellionCommand.Execute += ToggleChat;
+ _symbolPicker = new SymbolPicker();
+
Plugin.ClientState.Login += Login;
Plugin.ClientState.Logout += Logout;
@@ -792,6 +795,30 @@ public sealed class ChatLogWindow : Window
)
inputColour = ecColour;
+ // Symbol-picker trigger sits left of the channel indicator. ImRaii.Popup
+ // inside DrawAndConsume pins to the last rendered item, so the call MUST
+ // run immediately after this IconButton — placing it after the channel
+ // picker below would pin the popup under the wrong widget.
+ if (ImGuiUtil.IconButton(
+ FontAwesomeIcon.Smile,
+ "symbol-picker-trigger",
+ "Insert symbol or FFXIV icon"))
+ {
+ _symbolPicker.OpenPopup();
+ }
+ var insertedSymbol = _symbolPicker.DrawAndConsume();
+ if (insertedSymbol is not null)
+ {
+ // Same cursor-aware splice idiom as the AutoComplete commit path at
+ // ChatLogWindow.cs:2487-2493. Clamp because CursorPos can drift if
+ // the user mutates Chat while the popup is open.
+ var pos = Math.Clamp(CursorPos, 0, Chat.Length);
+ Chat = Chat[..pos] + insertedSymbol + Chat[pos..];
+ Activate = true;
+ ActivatePos = pos + insertedSymbol.Length;
+ }
+ ImGui.SameLine();
+
var beforeIcon = ImGui.GetCursorPos();
var tintSelector = Plugin.Config.ColorSelectedInputChannelButton && inputColour.HasValue;
diff --git a/HellionChat/Ui/SymbolPicker.cs b/HellionChat/Ui/SymbolPicker.cs
new file mode 100644
index 0000000..a252003
--- /dev/null
+++ b/HellionChat/Ui/SymbolPicker.cs
@@ -0,0 +1,124 @@
+using System.Numerics;
+using Dalamud.Bindings.ImGui;
+using Dalamud.Game.Text;
+using Dalamud.Interface.Utility.Raii;
+
+namespace HellionChat.Ui;
+
+// Popup picker for chat-input symbol insertion. Two tabs:
+// PUA — Dalamud's SeIconChar enum (161 server-safe FFXIV glyphs)
+// BMP — server-verified Unicode symbols (whitelist built 2026-05-XX)
+//
+// Render-only — the Settings-Guard for showing the trigger button lives on
+// the caller side (ChatLogWindow). Recent-Used is session state by design.
+internal sealed class SymbolPicker
+{
+ private const string PopupId = "HellionSymbolPicker";
+ private const int RecentCapacity = 16;
+
+ private string _search = string.Empty;
+ private readonly List _recentUsed = new(capacity: RecentCapacity);
+
+ public void OpenPopup() => ImGui.OpenPopup(PopupId);
+
+ // Returns the inserted codepoint as a string fragment if the user clicked
+ // one this frame, or null otherwise. Caller splices the fragment into the
+ // chat-input buffer at the current cursor position.
+ public string? DrawAndConsume()
+ {
+ // ImRaii.Popup mirrors ChatLogWindow.cs:823 / :2380, PayloadHandler.cs:68
+ // — auto-EndPopup via using-Dispose.
+ using var popup = ImRaii.Popup(PopupId);
+ if (!popup)
+ return null;
+
+ string? inserted = null;
+
+ using (var tabs = ImRaii.TabBar("##symbolpicker-tabs"))
+ {
+ if (tabs)
+ {
+ inserted = DrawPuaTab() ?? inserted;
+ inserted = DrawBmpTab() ?? inserted;
+ }
+ }
+
+ if (inserted is not null)
+ TrackRecent(inserted);
+
+ return inserted;
+ }
+
+ private string? DrawPuaTab()
+ {
+ using var tab = ImRaii.TabItem("FFXIV Icons");
+ if (!tab)
+ return null;
+
+ ImGui.InputTextWithHint("##pua-search", "Search by name (e.g. HighQuality)", ref _search, 64);
+
+ string? inserted = null;
+
+ if (ImGui.BeginChild("##pua-grid", new Vector2(0, 280), false))
+ {
+ var query = _search;
+ foreach (var icon in Enum.GetValues())
+ {
+ var label = icon.ToString();
+ if (query.Length > 0
+ && label.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
+ {
+ continue;
+ }
+
+ // ToIconString() returns the single-codepoint string ready for
+ // ImGui rendering (Dalamud SeIconCharExtensions.cs:25). Tooltip
+ // carries the enum name so users can discover what each glyph
+ // means.
+ if (ImGui.Selectable(
+ icon.ToIconString(),
+ false,
+ ImGuiSelectableFlags.DontClosePopups,
+ new Vector2(24, 24)))
+ {
+ inserted = icon.ToIconString();
+ }
+ if (ImGui.IsItemHovered())
+ ImGui.SetTooltip(label);
+
+ // Manually-wrapping pattern from imgui_demo.cpp on modern API.
+ // GetWindowContentRegionMax is obsolete since ImGui 1.92
+ // (imgui.h:565); HellionChat uses GetContentRegionAvail
+ // throughout (e.g. ChatLogWindow.cs:840). Same modern idiom.
+ var style = ImGui.GetStyle();
+ var lastItemX2 = ImGui.GetItemRectMax().X;
+ var availableRightX =
+ ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
+ if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
+ ImGui.SameLine();
+ }
+ }
+ ImGui.EndChild();
+
+ return inserted;
+ }
+
+ // Task 5 wires the BMP whitelist; the stub keeps the popup contract intact
+ // until then.
+ private string? DrawBmpTab() => null;
+
+ private void TrackRecent(string fragment)
+ {
+ if (string.IsNullOrEmpty(fragment) || fragment.Length > 4)
+ return;
+
+ var codepoint = (uint)char.ConvertToUtf32(fragment, 0);
+
+ // Move-to-front so the head stays the freshest pick.
+ _recentUsed.RemoveAll(c => c == codepoint);
+ _recentUsed.Insert(0, codepoint);
+
+ if (_recentUsed.Count > RecentCapacity)
+ _recentUsed.RemoveAt(_recentUsed.Count - 1);
+ }
+}
From 0e470fcdced3a4bddbd2b7be83c37c2dc82ad4e2 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 09:27:58 +0200
Subject: [PATCH 3/8] feat(ui): SymbolPicker BMP tab and session-only recents
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Second tab exposes the server-verified BMP whitelist (round-tripped via
/echo and /say in the v1.4.10 preflight). Recent-used row at the top
floats the user's last sixteen picks across both tabs, move-to-front
on reuse. Recents stay session-only by design — no Configuration touch,
schema unchanged.
---
HellionChat/Ui/SymbolPicker.cs | 183 ++++++++++++++++++++++++++++++++-
1 file changed, 179 insertions(+), 4 deletions(-)
diff --git a/HellionChat/Ui/SymbolPicker.cs b/HellionChat/Ui/SymbolPicker.cs
index a252003..2eb16d0 100644
--- a/HellionChat/Ui/SymbolPicker.cs
+++ b/HellionChat/Ui/SymbolPicker.cs
@@ -7,7 +7,7 @@ namespace HellionChat.Ui;
// Popup picker for chat-input symbol insertion. Two tabs:
// PUA — Dalamud's SeIconChar enum (161 server-safe FFXIV glyphs)
-// BMP — server-verified Unicode symbols (whitelist built 2026-05-XX)
+// BMP — server-verified Unicode symbols (whitelist built 2026-05-16)
//
// Render-only — the Settings-Guard for showing the trigger button lives on
// the caller side (ChatLogWindow). Recent-Used is session state by design.
@@ -19,6 +19,114 @@ internal sealed class SymbolPicker
private string _search = string.Empty;
private readonly List _recentUsed = new(capacity: RecentCapacity);
+ // FFXIV server-safe BMP symbols, verified 2026-05-16 via /echo + /say
+ // round-trip across four probe rounds (140 candidates tested, 43 filtered).
+ // Range U+2694-26C4 (Misc Symbols Extended), U+2700+ (Dingbats Extended),
+ // diagonal arrows, vulgar fractions U+2153+, and chess pieces all get
+ // dropped by the server, so they're not exposed here.
+ // Source-of-truth for this list lives in
+ // Projekte/FFXIV/Hellion Chat/Cycles/v1.4.10 BMP-Whitelist Notes.md.
+ private static readonly (uint Codepoint, string Name)[] BmpWhitelist = new[]
+ {
+ (0x00A1u, "Inverted Exclamation"),
+ (0x00A2u, "Cent Sign"),
+ (0x00A3u, "Pound Sign"),
+ (0x00A4u, "Currency Sign"),
+ (0x00A5u, "Yen Sign"),
+ (0x00A7u, "Section Sign"),
+ (0x00A9u, "Copyright Sign"),
+ (0x00ABu, "Left Angle Quote"),
+ (0x00AEu, "Registered Sign"),
+ (0x00B0u, "Degree Sign"),
+ (0x00B1u, "Plus-Minus Sign"),
+ (0x00B6u, "Pilcrow Sign"),
+ (0x00BBu, "Right Angle Quote"),
+ (0x00BCu, "One Quarter"),
+ (0x00BDu, "One Half"),
+ (0x00BEu, "Three Quarters"),
+ (0x00BFu, "Inverted Question"),
+ (0x00D7u, "Multiplication Sign"),
+ (0x00F7u, "Division Sign"),
+ (0x0393u, "Greek Capital Gamma"),
+ (0x0394u, "Greek Capital Delta"),
+ (0x0398u, "Greek Capital Theta"),
+ (0x039Bu, "Greek Capital Lambda"),
+ (0x039Eu, "Greek Capital Xi"),
+ (0x03A0u, "Greek Capital Pi"),
+ (0x03A3u, "Greek Capital Sigma"),
+ (0x03A6u, "Greek Capital Phi"),
+ (0x03A8u, "Greek Capital Psi"),
+ (0x03A9u, "Greek Capital Omega"),
+ (0x03B1u, "Greek Small Alpha"),
+ (0x03B2u, "Greek Small Beta"),
+ (0x03B3u, "Greek Small Gamma"),
+ (0x03B4u, "Greek Small Delta"),
+ (0x03B5u, "Greek Small Epsilon"),
+ (0x03B6u, "Greek Small Zeta"),
+ (0x03B7u, "Greek Small Eta"),
+ (0x03B8u, "Greek Small Theta"),
+ (0x03B9u, "Greek Small Iota"),
+ (0x03BAu, "Greek Small Kappa"),
+ (0x03BBu, "Greek Small Lambda"),
+ (0x03BCu, "Greek Small Mu"),
+ (0x03BDu, "Greek Small Nu"),
+ (0x03BEu, "Greek Small Xi"),
+ (0x03BFu, "Greek Small Omicron"),
+ (0x03C0u, "Greek Small Pi"),
+ (0x03C1u, "Greek Small Rho"),
+ (0x03C3u, "Greek Small Sigma"),
+ (0x03C4u, "Greek Small Tau"),
+ (0x03C5u, "Greek Small Upsilon"),
+ (0x03C6u, "Greek Small Phi"),
+ (0x03C7u, "Greek Small Chi"),
+ (0x03C8u, "Greek Small Psi"),
+ (0x03C9u, "Greek Small Omega"),
+ (0x2013u, "En Dash"),
+ (0x2014u, "Em Dash"),
+ (0x2020u, "Dagger"),
+ (0x2021u, "Double Dagger"),
+ (0x2026u, "Horizontal Ellipsis"),
+ (0x203Bu, "Reference Mark"),
+ (0x20ACu, "Euro Sign"),
+ (0x2122u, "Trade Mark Sign"),
+ (0x2190u, "Leftwards Arrow"),
+ (0x2191u, "Upwards Arrow"),
+ (0x2192u, "Rightwards Arrow"),
+ (0x2193u, "Downwards Arrow"),
+ (0x21D2u, "Rightwards Double Arrow"),
+ (0x21D4u, "Left Right Double Arrow"),
+ (0x2202u, "Partial Differential"),
+ (0x2207u, "Nabla"),
+ (0x2211u, "Summation"),
+ (0x221Au, "Square Root"),
+ (0x221Eu, "Infinity"),
+ (0x222Bu, "Integral"),
+ (0x2260u, "Not Equal To"),
+ (0x25A0u, "Black Square"),
+ (0x25A1u, "White Square"),
+ (0x25B2u, "Black Up Triangle"),
+ (0x25B3u, "White Up Triangle"),
+ (0x25BCu, "Black Down Triangle"),
+ (0x25C6u, "Black Diamond"),
+ (0x25C7u, "White Diamond"),
+ (0x25CBu, "White Circle"),
+ (0x25CFu, "Black Circle"),
+ (0x2600u, "Black Sun With Rays"),
+ (0x2601u, "Cloud"),
+ (0x2602u, "Umbrella"),
+ (0x2603u, "Snowman"),
+ (0x2605u, "Black Star"),
+ (0x2606u, "White Star"),
+ (0x2640u, "Female Sign"),
+ (0x2642u, "Male Sign"),
+ (0x2660u, "Black Spade Suit"),
+ (0x2661u, "White Heart Suit"),
+ (0x2663u, "Black Club Suit"),
+ (0x2665u, "Black Heart Suit"),
+ (0x266Au, "Eighth Note"),
+ (0x2713u, "Check Mark"),
+ };
+
public void OpenPopup() => ImGui.OpenPopup(PopupId);
// Returns the inserted codepoint as a string fragment if the user clicked
@@ -34,6 +142,29 @@ internal sealed class SymbolPicker
string? inserted = null;
+ // Recent-Used-Row sits above the tabs so both PUA and BMP picks share
+ // one fast-access strip. Session-only by design (see TrackRecent).
+ if (_recentUsed.Count > 0)
+ {
+ ImGui.TextDisabled("Recent");
+ ImGui.SameLine();
+ foreach (var codepoint in _recentUsed)
+ {
+ var glyph = char.ConvertFromUtf32((int)codepoint);
+ if (ImGui.Selectable(
+ glyph,
+ false,
+ ImGuiSelectableFlags.DontClosePopups,
+ new Vector2(20, 20)))
+ {
+ inserted = glyph;
+ }
+ ImGui.SameLine();
+ }
+ ImGui.NewLine();
+ ImGui.Separator();
+ }
+
using (var tabs = ImRaii.TabBar("##symbolpicker-tabs"))
{
if (tabs)
@@ -103,9 +234,53 @@ internal sealed class SymbolPicker
return inserted;
}
- // Task 5 wires the BMP whitelist; the stub keeps the popup contract intact
- // until then.
- private string? DrawBmpTab() => null;
+ private string? DrawBmpTab()
+ {
+ using var tab = ImRaii.TabItem("Symbols");
+ if (!tab)
+ return null;
+
+ ImGui.InputTextWithHint("##bmp-search", "Search by name (e.g. Heart)", ref _search, 64);
+
+ string? inserted = null;
+
+ if (ImGui.BeginChild("##bmp-grid", new Vector2(0, 280), false))
+ {
+ var query = _search;
+ foreach (var (codepoint, name) in BmpWhitelist)
+ {
+ if (query.Length > 0
+ && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
+ {
+ continue;
+ }
+
+ var glyph = char.ConvertFromUtf32((int)codepoint);
+ if (ImGui.Selectable(
+ glyph,
+ false,
+ ImGuiSelectableFlags.DontClosePopups,
+ new Vector2(24, 24)))
+ {
+ inserted = glyph;
+ }
+ if (ImGui.IsItemHovered())
+ ImGui.SetTooltip(name);
+
+ // Same manually-wrapping pattern as DrawPuaTab — modern API
+ // since GetWindowContentRegionMax was deprecated in ImGui 1.92.
+ var style = ImGui.GetStyle();
+ var lastItemX2 = ImGui.GetItemRectMax().X;
+ var availableRightX =
+ ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
+ if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
+ ImGui.SameLine();
+ }
+ }
+ ImGui.EndChild();
+
+ return inserted;
+ }
private void TrackRecent(string fragment)
{
From 679b8f0f5e62fd33904aa8f43590e95f83f3b7c9 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 10:05:37 +0200
Subject: [PATCH 4/8] feat(settings): toggle for the symbol-picker chat-input
button
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds a Configuration property, defaulted to enabled, and a checkbox in
the Chat settings tab's Behaviour section. Strings live in HellionStrings
so DE/EN stays in sync. Defaults aligned with our 'discoverable by
default, hidden by user choice' convention. Schema stays at v17 — the
new boolean is additive, the default constructor covers existing configs.
---
HellionChat/Configuration.cs | 2 ++
.../Resources/HellionStrings.Designer.cs | 4 ++++
HellionChat/Resources/HellionStrings.de.resx | 8 ++++++++
HellionChat/Resources/HellionStrings.resx | 8 ++++++++
HellionChat/Ui/ChatLogWindow.cs | 19 +++++++++++++------
HellionChat/Ui/SettingsTabs/Chat.cs | 6 ++++++
6 files changed, 41 insertions(+), 6 deletions(-)
diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs
index 2cc1cda..c7b0434 100755
--- a/HellionChat/Configuration.cs
+++ b/HellionChat/Configuration.cs
@@ -176,6 +176,7 @@ public class Configuration : IPluginConfiguration
public bool SortAutoTranslate;
public bool CollapseDuplicateMessages;
public bool CollapseKeepUniqueLinks;
+ public bool SymbolPickerEnabled = true;
public bool PlaySounds = true;
public bool KeepInputFocus = true;
public int MaxLinesToRender = 2_500; // 1-10000
@@ -270,6 +271,7 @@ public class Configuration : IPluginConfiguration
SortAutoTranslate = other.SortAutoTranslate;
CollapseDuplicateMessages = other.CollapseDuplicateMessages;
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
+ SymbolPickerEnabled = other.SymbolPickerEnabled;
PlaySounds = other.PlaySounds;
KeepInputFocus = other.KeepInputFocus;
MaxLinesToRender = other.MaxLinesToRender;
diff --git a/HellionChat/Resources/HellionStrings.Designer.cs b/HellionChat/Resources/HellionStrings.Designer.cs
index a9d7037..559a21a 100644
--- a/HellionChat/Resources/HellionStrings.Designer.cs
+++ b/HellionChat/Resources/HellionStrings.Designer.cs
@@ -270,6 +270,10 @@ internal class HellionStrings
internal static string Settings_Chat_Preview_Heading => Get(nameof(Settings_Chat_Preview_Heading));
internal static string Settings_Chat_Emotes_Heading => Get(nameof(Settings_Chat_Emotes_Heading));
+ // Hellion Chat — Chat-Tab SymbolPicker
+ internal static string Settings_Chat_SymbolPicker_Enable_Name => Get(nameof(Settings_Chat_SymbolPicker_Enable_Name));
+ internal static string Settings_Chat_SymbolPicker_Enable_Description => Get(nameof(Settings_Chat_SymbolPicker_Enable_Description));
+
// Hellion Chat — Database-Tab section headings
internal static string Settings_Database_Storage_Heading => Get(nameof(Settings_Database_Storage_Heading));
internal static string Settings_Database_Viewer_Heading => Get(nameof(Settings_Database_Viewer_Heading));
diff --git a/HellionChat/Resources/HellionStrings.de.resx b/HellionChat/Resources/HellionStrings.de.resx
index 59542f7..30d76f1 100644
--- a/HellionChat/Resources/HellionStrings.de.resx
+++ b/HellionChat/Resources/HellionStrings.de.resx
@@ -556,6 +556,14 @@
Emotes
+
+
+ Symbol-Picker-Button neben dem Chat-Eingang anzeigen
+
+
+ Fügt einen kleinen Button links neben dem Kanal-Indikator ein. Klick öffnet ein Popup mit FFXIV-Glyphen und einer kuratierten Symbol-Liste. Ausschalten für eine schlankere Eingabezeile.
+
+
Speicherung
diff --git a/HellionChat/Resources/HellionStrings.resx b/HellionChat/Resources/HellionStrings.resx
index f0a13b0..9de7345 100644
--- a/HellionChat/Resources/HellionStrings.resx
+++ b/HellionChat/Resources/HellionStrings.resx
@@ -556,6 +556,14 @@
Emotes
+
+
+ Show symbol-picker button next to chat input
+
+
+ Adds a small button left of the channel indicator that opens a popup with FFXIV icons and a curated symbol list. Disable if you prefer a leaner input bar.
+
+
Storage
diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs
index 6710716..0a3136b 100644
--- a/HellionChat/Ui/ChatLogWindow.cs
+++ b/HellionChat/Ui/ChatLogWindow.cs
@@ -799,13 +799,19 @@ public sealed class ChatLogWindow : Window
// inside DrawAndConsume pins to the last rendered item, so the call MUST
// run immediately after this IconButton — placing it after the channel
// picker below would pin the popup under the wrong widget.
- if (ImGuiUtil.IconButton(
- FontAwesomeIcon.Smile,
- "symbol-picker-trigger",
- "Insert symbol or FFXIV icon"))
+ if (Plugin.Config.SymbolPickerEnabled)
{
- _symbolPicker.OpenPopup();
+ if (ImGuiUtil.IconButton(
+ FontAwesomeIcon.Smile,
+ "symbol-picker-trigger",
+ "Insert symbol or FFXIV icon"))
+ {
+ _symbolPicker.OpenPopup();
+ }
}
+ // DrawAndConsume runs unconditionally; with the button hidden the popup
+ // can't open, so the call is a no-op. Splice path stays outside the
+ // guard for the same reason.
var insertedSymbol = _symbolPicker.DrawAndConsume();
if (insertedSymbol is not null)
{
@@ -817,7 +823,8 @@ public sealed class ChatLogWindow : Window
Activate = true;
ActivatePos = pos + insertedSymbol.Length;
}
- ImGui.SameLine();
+ if (Plugin.Config.SymbolPickerEnabled)
+ ImGui.SameLine();
var beforeIcon = ImGui.GetCursorPos();
diff --git a/HellionChat/Ui/SettingsTabs/Chat.cs b/HellionChat/Ui/SettingsTabs/Chat.cs
index d4ea21e..3115f66 100644
--- a/HellionChat/Ui/SettingsTabs/Chat.cs
+++ b/HellionChat/Ui/SettingsTabs/Chat.cs
@@ -139,6 +139,12 @@ internal sealed class Chat : ISettingsTab
);
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
}
+
+ ImGui.Checkbox(
+ HellionStrings.Settings_Chat_SymbolPicker_Enable_Name,
+ ref Mutable.SymbolPickerEnabled
+ );
+ ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_SymbolPicker_Enable_Description);
}
}
From f66316161b4d34c767be2ad808bf822372211f81 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 12:16:08 +0200
Subject: [PATCH 5/8] fix(autotells): preload tell history fully up to the
user-configured limit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
PreloadHistory had a hardcoded 500-row SQL scan window that capped the
per-partner history pull regardless of the AutoTellTabsHistoryPreload
setting. For active users with many tell partners, the scan window
filled up with chatter from other partners and pushed less-frequent
partners' history off the back end — pinned tabs reloaded empty even
though the messages were still in the database.
Drops the hardcoded scan cap. The (Receiver, Date) index keeps SQL fast
on the now-unbounded read, and the client-side loop still breaks as
soon as the configured per-tab limit is hit, so decode cost stays
proportional to the depth at which `limit` matches accumulate (typically
shallow even for chatty users).
---
HellionChat/MessageStore.cs | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/HellionChat/MessageStore.cs b/HellionChat/MessageStore.cs
index dd5f51f..bce9cae 100644
--- a/HellionChat/MessageStore.cs
+++ b/HellionChat/MessageStore.cs
@@ -1001,12 +1001,18 @@ internal class MessageStore : IDisposable
// SQL narrows by Receiver + ChatType (indexed); client does the final
// PlayerPayload comparison. sqlScanLimit caps the scan to stay within
// the message-processing worker thread budget.
+ // Walks the full receiver-filtered tell history newest-first and stops
+ // as soon as the per-partner match count reaches `limit`. The previous
+ // hardcoded 500-row scan window cut active users' less-frequent pinned
+ // partners out of the result whenever other partners' chatter pushed
+ // them off the back of the window. Index on (Receiver, Date) keeps the
+ // SQL side cheap; the client-side break bounds the actual decode cost
+ // to roughly the depth at which `limit` partner matches accumulate.
internal IReadOnlyList GetTellHistoryWithSender(
ulong receiver,
string senderName,
uint senderWorld,
- int limit,
- int sqlScanLimit = 500
+ int limit
)
{
if (limit <= 0)
@@ -1024,14 +1030,12 @@ internal class MessageStore : IDisposable
WHERE deleted = false
AND Receiver = $Receiver
AND ChatType IN ($TellIncoming, $TellOutgoing)
- ORDER BY Date DESC
- LIMIT $ScanLimit;
+ ORDER BY Date DESC;
";
cmd.CommandTimeout = 60;
cmd.Parameters.AddWithValue("$Receiver", receiver);
cmd.Parameters.AddWithValue("$TellIncoming", (int)ChatType.TellIncoming);
cmd.Parameters.AddWithValue("$TellOutgoing", (int)ChatType.TellOutgoing);
- cmd.Parameters.AddWithValue("$ScanLimit", sqlScanLimit);
var collected = new List();
using var enumerator = new MessageEnumerator(cmd.ExecuteReader(), _logger);
From 51f18e46a053410fcbbcb758cf8691f545c15372 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 12:49:01 +0200
Subject: [PATCH 6/8] chore(comments): tighten v1.4.10 inline commentary after
self-review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Five trim spots from the cycle's earlier commits — none change behaviour,
just drop redundant phrasing and stale references per the HellionChat
comment-style convention (1-3 lines default, link "same as X" instead of
repeating, file:line refs only where they aid navigation).
SymbolPicker:
- BmpWhitelist header consolidated to source + filter ranges
- ImRaii.Popup pattern links the established ChatLogWindow popup idiom
instead of citing three call-sites
- ToIconString comment drops the "discoverability" footnote that the
code already telegraphs
- Manually-wrapping comment drops the "same modern idiom" tail that
duplicated the preceding sentence
MessageStore:
- Merge the stale pre-v1.4.10 sqlScanLimit comment with the new
v1.4.10 commentary; the cap mention now describes the historical
reason rather than a parameter that no longer exists
---
HellionChat/MessageStore.cs | 17 ++++++-----------
HellionChat/Ui/SymbolPicker.cs | 28 +++++++++++-----------------
2 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/HellionChat/MessageStore.cs b/HellionChat/MessageStore.cs
index bce9cae..21d314a 100644
--- a/HellionChat/MessageStore.cs
+++ b/HellionChat/MessageStore.cs
@@ -997,17 +997,12 @@ internal class MessageStore : IDisposable
}
}
- // Returns up to limit tells exchanged with the named player, oldest-first.
- // SQL narrows by Receiver + ChatType (indexed); client does the final
- // PlayerPayload comparison. sqlScanLimit caps the scan to stay within
- // the message-processing worker thread budget.
- // Walks the full receiver-filtered tell history newest-first and stops
- // as soon as the per-partner match count reaches `limit`. The previous
- // hardcoded 500-row scan window cut active users' less-frequent pinned
- // partners out of the result whenever other partners' chatter pushed
- // them off the back of the window. Index on (Receiver, Date) keeps the
- // SQL side cheap; the client-side break bounds the actual decode cost
- // to roughly the depth at which `limit` partner matches accumulate.
+ // Returns up to `limit` tells exchanged with the named player, oldest-first.
+ // SQL narrows by Receiver + ChatType via the (Receiver, Date) index, then
+ // the client-side loop runs PlayerPayload comparison and breaks once
+ // `limit` partner matches accumulate. Earlier versions had a hardcoded
+ // 500-row scan cap that cut less-frequent pinned partners off the back of
+ // the window in chatty sessions; removed in v1.4.10.
internal IReadOnlyList GetTellHistoryWithSender(
ulong receiver,
string senderName,
diff --git a/HellionChat/Ui/SymbolPicker.cs b/HellionChat/Ui/SymbolPicker.cs
index 2eb16d0..0d0b28f 100644
--- a/HellionChat/Ui/SymbolPicker.cs
+++ b/HellionChat/Ui/SymbolPicker.cs
@@ -19,13 +19,10 @@ internal sealed class SymbolPicker
private string _search = string.Empty;
private readonly List _recentUsed = new(capacity: RecentCapacity);
- // FFXIV server-safe BMP symbols, verified 2026-05-16 via /echo + /say
- // round-trip across four probe rounds (140 candidates tested, 43 filtered).
- // Range U+2694-26C4 (Misc Symbols Extended), U+2700+ (Dingbats Extended),
- // diagonal arrows, vulgar fractions U+2153+, and chess pieces all get
- // dropped by the server, so they're not exposed here.
- // Source-of-truth for this list lives in
- // Projekte/FFXIV/Hellion Chat/Cycles/v1.4.10 BMP-Whitelist Notes.md.
+ // FFXIV server-safe BMP symbols, verified 2026-05-16 via /echo + /say.
+ // Filtered ranges: U+2694-26C4 (Misc Symbols Extended), U+2700+ (Dingbats
+ // Extended), diagonal arrows, U+2153+ fractions, chess pieces.
+ // Full probe log: Cycles/v1.4.10 BMP-Whitelist Notes.md.
private static readonly (uint Codepoint, string Name)[] BmpWhitelist = new[]
{
(0x00A1u, "Inverted Exclamation"),
@@ -134,8 +131,8 @@ internal sealed class SymbolPicker
// chat-input buffer at the current cursor position.
public string? DrawAndConsume()
{
- // ImRaii.Popup mirrors ChatLogWindow.cs:823 / :2380, PayloadHandler.cs:68
- // — auto-EndPopup via using-Dispose.
+ // ImRaii.Popup auto-disposes EndPopup, same idiom as other popups in
+ // ChatLogWindow.
using var popup = ImRaii.Popup(PopupId);
if (!popup)
return null;
@@ -202,10 +199,8 @@ internal sealed class SymbolPicker
continue;
}
- // ToIconString() returns the single-codepoint string ready for
- // ImGui rendering (Dalamud SeIconCharExtensions.cs:25). Tooltip
- // carries the enum name so users can discover what each glyph
- // means.
+ // ToIconString gives the single-codepoint glyph; tooltip
+ // carries the enum name for discoverability.
if (ImGui.Selectable(
icon.ToIconString(),
false,
@@ -217,10 +212,9 @@ internal sealed class SymbolPicker
if (ImGui.IsItemHovered())
ImGui.SetTooltip(label);
- // Manually-wrapping pattern from imgui_demo.cpp on modern API.
- // GetWindowContentRegionMax is obsolete since ImGui 1.92
- // (imgui.h:565); HellionChat uses GetContentRegionAvail
- // throughout (e.g. ChatLogWindow.cs:840). Same modern idiom.
+ // Manually-wrapping pattern from imgui_demo.cpp;
+ // GetWindowContentRegionMax obsolete since ImGui 1.92, use
+ // GetContentRegionAvail (see ChatLogWindow.cs:840).
var style = ImGui.GetStyle();
var lastItemX2 = ImGui.GetItemRectMax().X;
var availableRightX =
From 3e9117783347d3515ea6c9167120bb985d6e2772 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 13:25:53 +0200
Subject: [PATCH 7/8] release: bump to v1.4.10
---
HellionChat/HellionChat.csproj | 2 +-
HellionChat/HellionChat.yaml | 83 ++++++++++++++++++----------------
HellionChat/Plugin.cs | 4 +-
README.md | 39 ++++++++--------
docs/CHANGELOG.md | 25 ++++++++++
docs/ROADMAP.md | 31 +++++++++----
repo.json | 12 ++---
7 files changed, 119 insertions(+), 77 deletions(-)
diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj
index 6c0ea5b..90b7704 100644
--- a/HellionChat/HellionChat.csproj
+++ b/HellionChat/HellionChat.csproj
@@ -1,7 +1,7 @@
- 1.4.9
+ 1.4.10
enable
enable
diff --git a/HellionChat/HellionChat.yaml b/HellionChat/HellionChat.yaml
index eaf0c70..1166aee 100755
--- a/HellionChat/HellionChat.yaml
+++ b/HellionChat/HellionChat.yaml
@@ -35,6 +35,50 @@ tags:
- Replacement
- Privacy
changelog: |-
+ **v1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)**
+
+ Eleventh and final sub-patch of the v1.4.x polish-sweep series.
+ Symbol picker for the chat input, a tell-history reload fix for
+ users with many active partners, and a closing cleanup sweep
+ before v1.5.0 picks up the DI-container adoption.
+
+ - Symbol picker: a small smile-icon button left of the channel
+ indicator opens a popup with two tabs. The first lists all 161
+ FFXIV PUA glyphs (Dalamud's SeIconChar enum); the second
+ carries 97 server-verified BMP symbols (latin marks, currency,
+ the full Greek alphabet, geometric shapes, suits, notes) —
+ every one of them round-tripped through /echo and /say in a
+ four-round probe so the in-channel render matches what the
+ picker shows. Click drops the glyph at the caret, multi-insert
+ keeps the popup open, and a recent-used strip floats the last
+ sixteen picks across both tabs. Toggle in Settings → Chat →
+ Message behaviour, default on.
+ - Pinned auto-tell tabs reload their full history again: a
+ hidden 500-row scan cap in PreloadHistory used to override the
+ user-configurable AutoTellTabsHistoryPreload setting, so
+ less-frequent pinned partners (rare /tell sessions in an
+ otherwise busy week) lost their backlog. The cap is removed;
+ the (Receiver, Date) index keeps SQL fast, the client-side
+ loop still respects your setting as the upper bound.
+ - Slash-command teardown: /hellion, /hellionView,
+ /hellionDebugger (and #if DEBUG /hellionSeString) wrappers are
+ now cached as private fields. Plugin teardown detaches the
+ live registration instead of re-Register'ing with identical
+ args — closes a latent maintenance hazard from v1.4.9.
+ - v1.4.x polish-sweep wraps up here. The ImGuiListClipper render
+ refactor that was on the v1.4.10 reserve list got dropped
+ after cross-platform smoke showed the scroll rubber-band is a
+ Wine / Linux render-pipeline quirk, not universal — Windows
+ users never saw it. It will get its own platform-targeted
+ spike in a later patch. Next major cycle is v1.5.0 with the
+ DI-container adoption (Microsoft.Extensions.Hosting +
+ ILogger) modelled on Lightless.
+ - Migration v17 stays (no schema bump).
+
+ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
+
+ ---
+
**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame
@@ -150,43 +194,4 @@ changelog: |-
---
- **v1.4.6 — Code Hygiene and Refactor (2026-05-12)**
-
- Maintenance patch. No user-visible behaviour changes; tightens the
- development feedback loop, fixes two upstream-inherited bugs, and
- prepares the code for the v1.4.7 backlog cleanup.
-
- - preflight.sh gains a csharpier reflow check and a markdownlint
- pass so style drift and markdown violations are caught at the
- pre-push gate
- - FontManager fallback catches the full set of atlas-toolkit
- throws (IO, InvalidOperation, ArgumentException) — a corrupt
- font config no longer takes down the whole atlas build
- - BrandingLinks and IntegrationLinks URLs validated on plugin
- load — a typo in a future URL rotation now throws at startup
- - Cherry-picked from ChatTwo upstream f35b7d3: Chat.SetChannel
- no longer leaks the native Utf8String when the linkshell check
- rejects the channel
- - Cherry-picked from ChatTwo upstream f35b7d3: Tab.Clone now
- deep-clones UsedChannel and TellTarget — PopOut and Temp tabs
- no longer mutate each other's channel state
- - Active-tab underline scales with DPI and rounds to physical
- pixels for crisp rendering above 100% scaling
- - IconButton width parameter no longer subtracts HUD-scaled
- padding from a raw int (measured width passes through verbatim)
- - Internal: HellionStyle ChildBgAlpha extracted to a testable
- helper; Plugin.SaveConfig clones only the temp tabs;
- SettingsOverview caches the draw-list per frame;
- Dalamud.Utility.Util surface routed through an IPlatformUtil
- indirection (MessageStore IsWine probe is now testable in
- isolation)
- - Built-in themes: Crystal Nocturne (sapphire and electric
- magenta over obsidian, by CRYSTALLITE) replaces Moonlit Bloom.
- Users with Moonlit Bloom selected fall back to Hellion Arctic
- on first load
-
- Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
-
- ---
-
Full history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs
index d3aeb88..d4d0a5c 100755
--- a/HellionChat/Plugin.cs
+++ b/HellionChat/Plugin.cs
@@ -198,8 +198,8 @@ public sealed class Plugin : IAsyncDalamudPlugin
if (Config.Version < 16)
{
throw new InvalidOperationException(
- $"HellionChat v1.4.9 requires config schema v16, got v{Config.Version}. "
- + "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.9."
+ $"HellionChat v1.4.10 requires config schema v16, got v{Config.Version}. "
+ + "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
);
}
Config.Version = 17;
diff --git a/README.md b/README.md
index 9551f2d..c310630 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
[](LICENSE)
-[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
+[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
[](https://github.com/goatcorp/Dalamud)
[](https://dotnet.microsoft.com/)
[](https://www.finalfantasyxiv.com/)
@@ -11,7 +11,7 @@
-**Version 1.4.9** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
+**Version 1.4.10** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine comes from Chat 2
@@ -286,24 +286,23 @@ An optional submission to the Dalamud main plugin repo (in addition to the custo
## Project Status
-**Version 1.4.9** — Plugin-Load Render Polish. First-frame render cost is now well under Dalamud's 100 ms HITCH
-warning threshold (~76 ms median, down from ~127 ms). The gain comes from deferring six non-essential rendering
-sections on the very first Draw — bottom status bar, channel-name SeString chunks, window bounds check, hint
-banner, autocomplete and input-preview calculation — so the initial ImGui layout cost is spread between frame 0
-and frame 1 instead of all hitting at once. At 60 fps the user sees those sections one frame (~17 ms) later, which
-is invisible inside the post-reload font-atlas build window. Slash commands `/hellion`, `/hellionView`,
-`/hellionSeString` and `/hellionDebugger` are now registered centrally during plugin load so they work before
-their target window is opened the first time. The configuration-button entry in Dalamud's plugin manager hangs on
-the same path. Three plugin-load profiling logs (auto-translate warm-up, message-store connect, tab filter) stay
-on at Information level as a regression tripwire — if a future change pushes the load past 100 ms again, the cost
-is right there in `/xllog`. The release also ships a ChatTwo IPC compatibility layer: HellionChat now mirrors
-ChatTwo's full IPC surface (`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`,
-`Invoke`) under the `ChatTwo.*` namespace in addition to our existing `HellionChat.*` provider gates, so
-third-party integrations that historically only subscribe to ChatTwo's IPC (Artisan's and AllaganTools' context-
-menu hooks are the practical examples) keep working without requiring a code change on their side. Conflict
-detection prevents ChatTwo from loading in parallel with HellionChat, so there is no slot-collision risk at
-runtime. Migration v17 stays (no schema bump). Tenth sub-patch of the v1.4.x polish sweep series (as of
-2026-05-15).
+**Version 1.4.10** — Symbol-Picker and Tell-History Fix. Eleventh and final sub-patch of the v1.4.x polish sweep
+series. A new symbol-picker popup hangs off a smile-icon button left of the channel indicator: tab one lists all
+161 FFXIV PUA glyphs (Dalamud's `SeIconChar` enum); tab two carries 97 server-verified BMP symbols (latin marks,
+currency, the full Greek alphabet, geometric shapes, suits, notes) — each one round-tripped through `/echo` and
+`/say` in a four-round whitelist probe so the in-channel render matches what the picker shows. Click drops the
+glyph at the caret, multi-insert keeps the popup open, recent-used strip floats the last sixteen picks across
+both tabs. Toggle in Settings → Chat → Message behaviour, default on. Mid-cycle hotfix for pinned auto-tell tabs:
+PreloadHistory had a hidden 500-row SQL scan cap that overrode the user-configurable `AutoTellTabsHistoryPreload`
+setting — active users with many tell partners lost the backlog of less-frequent pinned partners. The cap is
+removed; the `(Receiver, Date)` index keeps SQL fast, the client-side loop respects the user setting as the upper
+bound. Slash-command teardown cleanup: `/hellion`, `/hellionView`, `/hellionDebugger` (and `#if DEBUG /hellionSeString`)
+wrappers are cached as private fields so plugin teardown detaches the live registration instead of re-Register'ing
+with identical args. The original Reserve-A `ImGuiListClipper` refactor for `DrawMessages` was cancelled after
+cross-platform smoke showed the scroll rubber-band is a Wine/Linux render-pipeline quirk, not universal — Windows
+users on v1.4.9 never saw it; the spike that targets the Wine path lives in a later patch. Migration v17 stays
+(no schema bump). v1.4.x polish sweep wraps up here; next major cycle is v1.5.0 with the DI-container adoption
+(`Microsoft.Extensions.Hosting` + `ILogger`) modelled on Lightless (as of 2026-05-16).
Hellion Chat is a standalone plugin, no longer a fork in the repository sense. Fully completed:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 59d16dd..62c10da 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -10,6 +10,31 @@ to the release pages for details.
---
+## Hellion Chat 1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)
+
+Eleventh and final sub-patch of the v1.4.x Polish-Sweep series. Symbol picker for the chat input, a tell-history reload fix
+for users with many active partners, and a closing cleanup sweep before v1.5.0 picks up the DI-container adoption.
+
+- Symbol picker for the chat input: smile-icon button left of the channel indicator opens a popup with two tabs —
+ 161 FFXIV PUA glyphs (Dalamud's SeIconChar enum) and 97 server-verified BMP symbols round-tripped through `/echo` and
+ `/say` in a four-round probe. Cursor-aware splice, multi-insert keeps the popup open, recent-used strip floats the last
+ sixteen picks across both tabs. Toggle in Settings → Chat → Message behaviour, default on.
+- Pinned auto-tell tabs reload their full history again. PreloadHistory had a hidden 500-row scan cap that overrode the
+ user-configurable `AutoTellTabsHistoryPreload` setting whenever you chatted with many partners; less-frequent pinned
+ partners lost their backlog. The cap is removed.
+- Slash-command teardown cleanup: `/hellion`, `/hellionView`, `/hellionDebugger` (and `#if DEBUG /hellionSeString`) wrappers
+ are now cached as private fields so plugin teardown detaches the live registration instead of re-Register'ing with
+ identical args (latent maintenance hazard from v1.4.9).
+- v1.4.x Polish-Sweep wraps up here. The ImGuiListClipper render refactor that was on the v1.4.10 reserve list got dropped
+ after cross-platform smoke showed the scroll rubber-band is a Wine/Linux render-pipeline quirk, not universal — Windows
+ users never saw it. It will get its own platform-targeted spike in a later patch. Next major cycle is v1.5.0 with the
+ DI-container adoption (Microsoft.Extensions.Hosting + ILogger) modelled on Lightless.
+- Migration v17 stays (no schema bump).
+
+Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
+
+---
+
## Hellion Chat 1.4.9 — Plugin-Load Render Polish (2026-05-15)
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median down to
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index cec3ffc..5dca8f9 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -10,16 +10,29 @@ the plugin's privacy-first scope during brainstorming.
---
-## Next Cycle (v1.4.10)
+## Next Cycle (v1.5.0)
-**Render Clipper, Symbol-Picker and Final-Cleanup.** Reserve items inherited from the v1.4.9 plan that did not need to
-land in the HITCH-cut: an `ImGuiListClipper` for variable-height messages in `DrawMessages` (the OtterGui `ImGuiClip.cs`
-wrapper is the idiom anchor), a Symbol Picker popup for the chat input (`imgui_demo.cpp` Popups & Modal Windows section
-is the pattern reference), plus the carry-over from v1.4.9: structural First-Frame-Layout rewrite if the v1.4.9 selective
-defers turn out to be too narrow once user-side regressions surface. Lazy-Window-Init naive is **not** in scope — the
-v1.4.9 Stage-2 diagnose falsified that path (`WindowSystem.windows` is non-thread-safe, Game-Freeze under reload stress,
-no measurable HITCH delta). A clean DI-container adoption (Lightless `PluginHostFactory` pattern) belongs in v1.5.x and
-will revisit the question with the right threading model.
+**DI-container adoption.** Microsoft.Extensions.Hosting plus `ILogger` modelled on Lightless's `PluginHostFactory`
+pattern. The v1.4.x Polish-Sweep series is closed; v1.5.0 starts the structural cycle that the smaller F12.x indirection
+shims (`IPluginLogProxy`, `IPlatformUtil`) were paving the way for. After that, the Wine/Linux scroll-rubber-band spike
+deferred from v1.4.10 (Reserve-A cancelled — Windows users never saw it) plus the First-Run-Wizard rework that lets users
+opt into the curated defaults instead of just picking a privacy profile.
+
+---
+
+## v1.4.10 — Symbol-Picker and Tell-History Fix (released 2026-05-16)
+
+Eleventh and final sub-patch of the v1.4.x Polish Sweep series. Symbol picker for the chat input — popup with two tabs
+(161 FFXIV PUA glyphs via Dalamud's SeIconChar plus 97 server-verified BMP symbols probed through `/echo` and `/say` in
+a four-round whitelist build) — cursor-aware splice, multi-insert, recent-used strip across both tabs, Settings toggle
+in Chat → Message behaviour. Mid-cycle hotfix for pinned auto-tell tabs: PreloadHistory used to cap the SQL scan at
+500 rows regardless of the user's `AutoTellTabsHistoryPreload` setting, so active users with many partners lost the
+backlog of less-frequent pinned partners; the cap is gone, the `(Receiver, Date)` index keeps SQL fast, the client-side
+loop respects the user setting as the upper bound. Slash-command teardown cleanup wires the v1.4.9 wrappers through
+private fields so dispose detaches the live registration instead of re-registering with identical args. The original
+Reserve-A `ImGuiListClipper` refactor for `DrawMessages` was cancelled after cross-platform smoke showed the scroll
+rubber-band is a Wine/Linux render-pipeline quirk, not universal — Windows-side testing on v1.4.9 confirmed no lag.
+Migration v17 stays.
---
diff --git a/repo.json b/repo.json
index ca1f9ac..6e3095d 100644
--- a/repo.json
+++ b/repo.json
@@ -3,7 +3,7 @@
"Author": "Jon Kazama (Hellion Forge)",
"Name": "Hellion Chat",
"InternalName": "HellionChat",
- "AssemblyVersion": "1.4.9.0",
+ "AssemblyVersion": "1.4.10.0",
"Description": "A Hellion Forge plugin — privacy-focused chat replacement for FINAL FANTASY XIV, built for EU, US and JP data rules.\n\nBy default only your own conversations are stored. Public chat, NPC dialogue, system messages and battle logs are discarded at the storage layer unless you opt in. Retention windows are configurable per channel, history can be wiped retroactively, and everything can be exported on demand.\n\nFeatures:\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with preview and Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three profiles: Privacy-First, Casual, Full History\n- Bilingual UI (EN/DE) with live language switching\n- Own config and database — no shared state with other plugins\n\nBased on Chat 2 by Infi and Anna (EUPL-1.2).\nSupport: https://discord.gg/X9V7Kcv5gR",
"ApplicableVersion": "any",
"RepoUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat",
@@ -14,12 +14,12 @@
"CanUnloadAsync": false,
"LoadPriority": 0,
"Punchline": "A Hellion Forge plugin. Privacy-first chat for FFXIV, built to stay out of your way.",
- "Changelog": "**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**\n\nTenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median to ~76 ms median, comfortably under Dalamud's 100 ms HITCH warning threshold.\n\n- First-frame defer: six non-essential rendering sections inside ChatLogWindow skip their first Draw and run one frame later (bottom status bar, channel-name SeString chunks, window bounds check, v0.6.1 hint banner, autocomplete, input-preview calculation). User-visible delay is ~17 ms at 60 fps, hidden inside the post-reload font-atlas build window.\n- Slash-command centralisation: /hellion, /hellionView, /hellionSeString and /hellionDebugger are registered in LoadAsync instead of inside the corresponding window constructors. The plugin-manager Open and configuration buttons hang on the same path.\n- Plugin-load profiling logs stay on at Information level (MessageStore connect/migrate, FilterAllTabs, auto-translate warmup) as a regression tripwire — a future load past 100 ms will show up in /xllog without a Debug filter.\n- ChatTwo IPC compatibility layer: HellionChat now mirrors ChatTwo's full IPC surface (GetChatInputState, ChatInputStateChanged, Register, Unregister, Available, Invoke) under the ChatTwo.* namespace in addition to our existing HellionChat.* provider gates. Third-party integrations that historically only subscribe to ChatTwo's IPC — for example Artisan's and AllaganTools' context-menu hooks — keep working without requiring a code change on their side. Conflict detection prevents ChatTwo from loading in parallel with HellionChat, so there is no slot-collision risk at runtime.\n- Migration v17 stays (no schema bump).\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)**\n\nNinth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (DbViewer FTS5 full-text search, ad-block foundation investigation) plus three polish quick-wins.\n\n- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously on first load after the update with a progress toast. The local page-filter remains available as the default mode. Queries match as exact phrases -- multi-word terms must appear together in order; advanced users can opt into raw FTS5 MATCH syntax by wrapping their own double-quotes.\n- Custom theme files now auto-reload when edited while the theme is active -- no need to re-click the theme in the picker.\n- Retention sweep no longer blocks the framework thread, removing the ~194ms mini-hitch per sweep.\n- Status bar renders correctly at Windows display scaling > 100%.\n- Receive-suppressed-tells routing investigated this cycle and postponed to v1.5.x: when other plugins suppress tells via CheckMessageHandled, the FFXIV chat pipeline skips the RaptureLogModule.AddMsgSourceEntry path so HellionChat's ContentIdResolverHook does not fire and tell-partner identification breaks. The fix belongs next to the planned ad-block hook layer where the same patch surface comes up.\n- Internal: messages.Id is declared BLOB but stored as TEXT (Microsoft.Data.Sqlite Guid binding). FTS bulk insert and LoadByGuids match the TEXT storage form on both sides. Migration v17 stays (no schema bump).\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.7 — Backlog Cleanup and Mid-Features (2026-05-13)**\n\nEighth sub-patch of the v1.4.x polish-sweep series. First user-visible feature bundle since v1.4.5 — pinned tell tabs that survive relog, opt-in Honorific glow rendering, and a configurable sidebar.\n\n- TempTell Pin: right-click a TempTell tab in the sidebar to pin it. Pinned tabs survive relog, keep their conversation history (loaded on demand from the message store), and stay bound to the same /tell partner. Hard cap of 5 pinned tabs in a pool separate from the 15-tab auto-tell pool — total ceiling is 20 tabs. New 'Angepinnt' / 'Pinned' section in the sidebar with its own divider header\n- Honorific Glow outline now renders when the title carries a Glow colour. Opt-in via Settings → Integrations → 'Render glow outlines (Honorific)' (default off, dodges the per-frame DrawList overhead on low-end hardware). Gradient (Color3 / GradientColourSet / Wave / Pulse) is parsed but rendered statically — a later cycle will port the full animation\n- Sidebar width is now configurable in Theme & Layout (range 44–160 px). Default stays icon-only; widen to fit section headers like 'Aktive Tells (3)' without truncation\n- Settings Save no longer pops the chat input back to /tell with a pinned partner — Configuration.UpdateFrom now preserves the runtime CurrentChannel across the persistent-tab merge, and TabSwitched deep-clones the seeded channel instead of sharing the previous tab's UsedChannel\n- Util/ImGuiUtil.cs DrawArrows IconButton id now uses (id + 1).ToString() instead of the operator-precedence quirk id + 1.ToString() — generated IDs stay numerically stable\n- Internal: IPluginLogProxy indirection over Dalamud's IPluginLog routes all ~91 Plugin.Log call sites through a testable proxy. MessageStore.Migrate0 can now run in xUnit without loading Dalamud.dll, closing the gap F12.1 left in v1.4.6\n- Internal: TempTab counter switched from an Interlocked cached field to a derived Tabs.Count(predicate) — pin-state transitions are cold-path and don't need lock-free reads\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.6 — Code Hygiene and Refactor (2026-05-12)**\n\nMaintenance patch. No user-visible behaviour changes; tightens the development feedback loop, fixes two upstream-inherited bugs, and prepares the code for the v1.4.7 backlog cleanup.\n\n- preflight.sh gains a csharpier reflow check and a markdownlint pass so style drift and markdown violations are caught at the pre-push gate\n- FontManager fallback catches the full set of atlas-toolkit throws (IO, InvalidOperation, ArgumentException) — a corrupt font config no longer takes down the whole atlas build\n- BrandingLinks and IntegrationLinks URLs validated on plugin load — a typo in a future URL rotation now throws at startup\n- Cherry-picked from ChatTwo upstream f35b7d3: Chat.SetChannel no longer leaks the native Utf8String when the linkshell check rejects the channel\n- Cherry-picked from ChatTwo upstream f35b7d3: Tab.Clone now deep-clones UsedChannel and TellTarget — PopOut and Temp tabs no longer mutate each other's channel state\n- Active-tab underline scales with DPI and rounds to physical pixels for crisp rendering above 100% scaling\n- IconButton width parameter no longer subtracts HUD-scaled padding from a raw int (measured width passes through verbatim)\n- Internal: HellionStyle ChildBgAlpha extracted to a testable helper; Plugin.SaveConfig clones only the temp tabs; SettingsOverview caches the draw-list per frame; Dalamud.Utility.Util surface routed through an IPlatformUtil indirection (MessageStore IsWine probe is now testable in isolation)\n- Built-in themes: Crystal Nocturne (sapphire and electric magenta over obsidian, by CRYSTALLITE) replaces Moonlit Bloom. Users with Moonlit Bloom selected fall back to Hellion Arctic on first load\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\nFull history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases",
+ "Changelog": "**v1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)**\n\nEleventh and final sub-patch of the v1.4.x polish-sweep series. Symbol picker for the chat input, a tell-history reload fix for users with many active partners, and a closing cleanup sweep before v1.5.0 picks up the DI-container adoption.\n\n- Symbol picker: a small smile-icon button left of the channel indicator opens a popup with two tabs. The first lists all 161 FFXIV PUA glyphs (Dalamud's SeIconChar enum); the second carries 97 server-verified BMP symbols (latin marks, currency, the full Greek alphabet, geometric shapes, suits, notes) — every one of them round-tripped through /echo and /say in a four-round probe so the in-channel render matches what the picker shows. Click drops the glyph at the caret, multi-insert keeps the popup open, and a recent-used strip floats the last sixteen picks across both tabs. Toggle in Settings → Chat → Message behaviour, default on.\n- Pinned auto-tell tabs reload their full history again: a hidden 500-row scan cap in PreloadHistory used to override the user-configurable AutoTellTabsHistoryPreload setting, so less-frequent pinned partners (rare /tell sessions in an otherwise busy week) lost their backlog. The cap is removed; the (Receiver, Date) index keeps SQL fast, the client-side loop still respects your setting as the upper bound.\n- Slash-command teardown: /hellion, /hellionView, /hellionDebugger (and #if DEBUG /hellionSeString) wrappers are now cached as private fields. Plugin teardown detaches the live registration instead of re-Register'ing with identical args — closes a latent maintenance hazard from v1.4.9.\n- v1.4.x polish-sweep wraps up here. The ImGuiListClipper render refactor that was on the v1.4.10 reserve list got dropped after cross-platform smoke showed the scroll rubber-band is a Wine / Linux render-pipeline quirk, not universal — Windows users never saw it. It will get its own platform-targeted spike in a later patch. Next major cycle is v1.5.0 with the DI-container adoption (Microsoft.Extensions.Hosting + ILogger) modelled on Lightless.\n- Migration v17 stays (no schema bump).\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**\n\nTenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median to ~76 ms median, comfortably under Dalamud's 100 ms HITCH warning threshold.\n\n- First-frame defer: six non-essential rendering sections inside ChatLogWindow skip their first Draw and run one frame later (bottom status bar, channel-name SeString chunks, window bounds check, v0.6.1 hint banner, autocomplete, input-preview calculation). User-visible delay is ~17 ms at 60 fps, hidden inside the post-reload font-atlas build window.\n- Slash-command centralisation: /hellion, /hellionView, /hellionSeString and /hellionDebugger are registered in LoadAsync instead of inside the corresponding window constructors. The plugin-manager Open and configuration buttons hang on the same path.\n- Plugin-load profiling logs stay on at Information level (MessageStore connect/migrate, FilterAllTabs, auto-translate warmup) as a regression tripwire — a future load past 100 ms will show up in /xllog without a Debug filter.\n- ChatTwo IPC compatibility layer: HellionChat now mirrors ChatTwo's full IPC surface (GetChatInputState, ChatInputStateChanged, Register, Unregister, Available, Invoke) under the ChatTwo.* namespace in addition to our existing HellionChat.* provider gates. Third-party integrations that historically only subscribe to ChatTwo's IPC — for example Artisan's and AllaganTools' context-menu hooks — keep working without requiring a code change on their side. Conflict detection prevents ChatTwo from loading in parallel with HellionChat, so there is no slot-collision risk at runtime.\n- Migration v17 stays (no schema bump).\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)**\n\nNinth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (DbViewer FTS5 full-text search, ad-block foundation investigation) plus three polish quick-wins.\n\n- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously on first load after the update with a progress toast. The local page-filter remains available as the default mode. Queries match as exact phrases -- multi-word terms must appear together in order; advanced users can opt into raw FTS5 MATCH syntax by wrapping their own double-quotes.\n- Custom theme files now auto-reload when edited while the theme is active -- no need to re-click the theme in the picker.\n- Retention sweep no longer blocks the framework thread, removing the ~194ms mini-hitch per sweep.\n- Status bar renders correctly at Windows display scaling > 100%.\n- Receive-suppressed-tells routing investigated this cycle and postponed to v1.5.x: when other plugins suppress tells via CheckMessageHandled, the FFXIV chat pipeline skips the RaptureLogModule.AddMsgSourceEntry path so HellionChat's ContentIdResolverHook does not fire and tell-partner identification breaks. The fix belongs next to the planned ad-block hook layer where the same patch surface comes up.\n- Internal: messages.Id is declared BLOB but stored as TEXT (Microsoft.Data.Sqlite Guid binding). FTS bulk insert and LoadByGuids match the TEXT storage form on both sides. Migration v17 stays (no schema bump).\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\n**v1.4.7 — Backlog Cleanup and Mid-Features (2026-05-13)**\n\nEighth sub-patch of the v1.4.x polish-sweep series. First user-visible feature bundle since v1.4.5 — pinned tell tabs that survive relog, opt-in Honorific glow rendering, and a configurable sidebar.\n\n- TempTell Pin: right-click a TempTell tab in the sidebar to pin it. Pinned tabs survive relog, keep their conversation history (loaded on demand from the message store), and stay bound to the same /tell partner. Hard cap of 5 pinned tabs in a pool separate from the 15-tab auto-tell pool — total ceiling is 20 tabs. New 'Angepinnt' / 'Pinned' section in the sidebar with its own divider header\n- Honorific Glow outline now renders when the title carries a Glow colour. Opt-in via Settings → Integrations → 'Render glow outlines (Honorific)' (default off, dodges the per-frame DrawList overhead on low-end hardware). Gradient (Color3 / GradientColourSet / Wave / Pulse) is parsed but rendered statically — a later cycle will port the full animation\n- Sidebar width is now configurable in Theme & Layout (range 44–160 px). Default stays icon-only; widen to fit section headers like 'Aktive Tells (3)' without truncation\n- Settings Save no longer pops the chat input back to /tell with a pinned partner — Configuration.UpdateFrom now preserves the runtime CurrentChannel across the persistent-tab merge, and TabSwitched deep-clones the seeded channel instead of sharing the previous tab's UsedChannel\n- Util/ImGuiUtil.cs DrawArrows IconButton id now uses (id + 1).ToString() instead of the operator-precedence quirk id + 1.ToString() — generated IDs stay numerically stable\n- Internal: IPluginLogProxy indirection over Dalamud's IPluginLog routes all ~91 Plugin.Log call sites through a testable proxy. MessageStore.Migrate0 can now run in xUnit without loading Dalamud.dll, closing the gap F12.1 left in v1.4.6\n- Internal: TempTab counter switched from an Interlocked cached field to a derived Tabs.Count(predicate) — pin-state transitions are cold-path and don't need lock-free reads\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\nFull history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases",
"AcceptsFeedback": true,
- "DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.9/latest.zip",
- "DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.9/latest.zip",
- "DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.9/latest.zip",
- "TestingAssemblyVersion": "1.4.9.0",
+ "DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.10/latest.zip",
+ "DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.10/latest.zip",
+ "DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v1.4.10/latest.zip",
+ "TestingAssemblyVersion": "1.4.10.0",
"IconUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png",
"ImageUrls": [
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png",
From 667950c98e28df3d080cc3d2ef16feae724241b2 Mon Sep 17 00:00:00 2001
From: Jon Kazama
Date: Sat, 16 May 2026 14:01:17 +0200
Subject: [PATCH 8/8] docs: add v1.4.10 forge announcement post and apply
csharpier reflow
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Forge-Post is required in the tagged tree so forge-announce.yml can read
it during the release-pipeline run. Plus a csharpier reflow on two files
(SymbolPicker.cs, ChatLogWindow.cs) that preflight Block E flagged after
the cycle's comment-tightening sweep — purely whitespace, no behaviour
change.
---
.github/forge-posts/v1.4.10.md | 36 ++++++++++++++++++++++++++++++++
HellionChat/Ui/ChatLogWindow.cs | 7 +++++--
HellionChat/Ui/SymbolPicker.cs | 37 +++++++++++++++++++++++----------
3 files changed, 67 insertions(+), 13 deletions(-)
create mode 100644 .github/forge-posts/v1.4.10.md
diff --git a/.github/forge-posts/v1.4.10.md b/.github/forge-posts/v1.4.10.md
new file mode 100644
index 0000000..8053b09
--- /dev/null
+++ b/.github/forge-posts/v1.4.10.md
@@ -0,0 +1,36 @@
+---
+subtitle: Symbol-Picker und Tell-History Fix
+versionsnatur: Feature-Patch + Hotfix
+---
+
+- Symbol-Picker im Chat-Eingang: ein kleiner Smile-Button links neben
+ dem Kanal-Indikator öffnet ein Popup mit zwei Tabs. Der erste listet
+ alle 161 FFXIV-PUA-Glyphen (Dalamuds SeIconChar); der zweite trägt
+ 97 verifizierte BMP-Symbole (Latin-Marken, Währungen, das ganze
+ griechische Alphabet, Geometrie, Spielkarten, Noten) — jedes davon
+ über `/echo` und `/say` in einer vierrundigen Whitelist-Probe
+ durchgereicht, damit der Channel-Render dem entspricht, was der
+ Picker anzeigt. Klick fügt das Symbol an der Cursor-Position ein,
+ Multi-Insert lässt das Popup offen, eine Recent-Used-Leiste zeigt
+ die letzten sechzehn Picks über beide Tabs. Toggle in Settings →
+ Chat → Nachrichten-Verhalten, Default an.
+- Verlauf in angepinnten Tell-Tabs lädt wieder vollständig: ein
+ versteckter 500-Zeilen-Scan-Cap in PreloadHistory hat das
+ User-Setting `AutoTellTabsHistoryPreload` überschrieben, wodurch
+ weniger-frequente Tell-Partner ihren Backlog verloren haben sobald
+ die Scan-Schicht mit anderen Chat-Partnern voll lief. Cap ist raus,
+ der Index auf `(Receiver, Date)` hält die Query schnell.
+- Slash-Command-Teardown: /hellion, /hellionView, /hellionDebugger
+ (und im Debug-Build /hellionSeString) sind als private Felder
+ gecached. Plugin-Dispose detached die echte Registrierung, statt
+ mit identischen Args neu zu registrieren — schließt eine latente
+ Wartungs-Falle aus v1.4.9.
+- v1.4.x-Polish-Sweep endet hier. Der ImGuiListClipper-Refactor von
+ der v1.4.10-Reserve-Liste wurde gecancelt, nachdem der Cross-
+ Plattform-Smoke gezeigt hat dass das Scroll-Gummi ein Wine/Linux-
+ Quirk ist — Windows-User haben es nie gesehen. Spike dafür kommt in
+ einem späteren Patch. Nächster Major-Cycle ist v1.5.0 mit der
+ DI-Container-Adoption (`Microsoft.Extensions.Hosting` +
+ `ILogger`) nach dem Lightless-Vorbild.
+- Migration v17 unverändert: kein Schema-Bump, kein
+ Config-Migrations-Aufwand.
diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs
index 0a3136b..ca990aa 100644
--- a/HellionChat/Ui/ChatLogWindow.cs
+++ b/HellionChat/Ui/ChatLogWindow.cs
@@ -801,10 +801,13 @@ public sealed class ChatLogWindow : Window
// picker below would pin the popup under the wrong widget.
if (Plugin.Config.SymbolPickerEnabled)
{
- if (ImGuiUtil.IconButton(
+ if (
+ ImGuiUtil.IconButton(
FontAwesomeIcon.Smile,
"symbol-picker-trigger",
- "Insert symbol or FFXIV icon"))
+ "Insert symbol or FFXIV icon"
+ )
+ )
{
_symbolPicker.OpenPopup();
}
diff --git a/HellionChat/Ui/SymbolPicker.cs b/HellionChat/Ui/SymbolPicker.cs
index 0d0b28f..bfc426f 100644
--- a/HellionChat/Ui/SymbolPicker.cs
+++ b/HellionChat/Ui/SymbolPicker.cs
@@ -148,11 +148,14 @@ internal sealed class SymbolPicker
foreach (var codepoint in _recentUsed)
{
var glyph = char.ConvertFromUtf32((int)codepoint);
- if (ImGui.Selectable(
+ if (
+ ImGui.Selectable(
glyph,
false,
ImGuiSelectableFlags.DontClosePopups,
- new Vector2(20, 20)))
+ new Vector2(20, 20)
+ )
+ )
{
inserted = glyph;
}
@@ -183,7 +186,12 @@ internal sealed class SymbolPicker
if (!tab)
return null;
- ImGui.InputTextWithHint("##pua-search", "Search by name (e.g. HighQuality)", ref _search, 64);
+ ImGui.InputTextWithHint(
+ "##pua-search",
+ "Search by name (e.g. HighQuality)",
+ ref _search,
+ 64
+ );
string? inserted = null;
@@ -193,19 +201,24 @@ internal sealed class SymbolPicker
foreach (var icon in Enum.GetValues())
{
var label = icon.ToString();
- if (query.Length > 0
- && label.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
+ if (
+ query.Length > 0
+ && label.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0
+ )
{
continue;
}
// ToIconString gives the single-codepoint glyph; tooltip
// carries the enum name for discoverability.
- if (ImGui.Selectable(
+ if (
+ ImGui.Selectable(
icon.ToIconString(),
false,
ImGuiSelectableFlags.DontClosePopups,
- new Vector2(24, 24)))
+ new Vector2(24, 24)
+ )
+ )
{
inserted = icon.ToIconString();
}
@@ -243,18 +256,20 @@ internal sealed class SymbolPicker
var query = _search;
foreach (var (codepoint, name) in BmpWhitelist)
{
- if (query.Length > 0
- && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
+ if (query.Length > 0 && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
{
continue;
}
var glyph = char.ConvertFromUtf32((int)codepoint);
- if (ImGui.Selectable(
+ if (
+ ImGui.Selectable(
glyph,
false,
ImGuiSelectableFlags.DontClosePopups,
- new Vector2(24, 24)))
+ new Vector2(24, 24)
+ )
+ )
{
inserted = glyph;
}