chore(linting): refresh configs and sweep auto-fix

Pull in the refreshed linter and tooling configs (editorconfig,
gitignore, gitattributes, prettierignore, prettierrc, markdownlint,
yamllint, env.example, dotnet-tools) and run prettier and markdownlint
in --fix / --write mode across the repo so the existing tree matches
the new rules.

- prettier 2-space indent on yaml/yml and json overrides, asterisk
  strong, underscore emphasis, proseWrap always
- markdownlint MD007 indent aligned to 2 and MD049 to underscore so
  prettier output stays passing
- preflight Block F also ignores CLAUDE.md (gitignored personal file)
- prettierignore extended to keep HellionChat.yaml manifest and the
  NuGet packages.lock.json out of the formatter

No semantic content changed; csharpier, build, full build-suite
(729/729) and the new prettier/markdownlint/yamllint checks all green.
This commit is contained in:
2026-05-17 17:20:55 +02:00
parent 2315f10d91
commit 0220e5d756
53 changed files with 3501 additions and 2630 deletions
+25 -19
View File
@@ -1,13 +1,15 @@
# AI Assistance Disclosure
HellionChat uses AI assistance per the [Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/)
at the **Pair** level.
HellionChat uses AI assistance per the
[Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/) at the **Pair**
level.
A note up front: HellionChat is currently not submitted to the official Dalamud plugin repository and technically has no
obligation to disclose this. I would rather be upfront about how it is built.
A note up front: HellionChat is currently not submitted to the official Dalamud plugin repository
and technically has no obligation to disclose this. I would rather be upfront about how it is built.
HellionChat is my entry point into game modding and plugin development. I have never written a plugin for a game before.
I work alone, so I get help where I need it. That is not something I want to hide.
HellionChat is my entry point into game modding and plugin development. I have never written a
plugin for a game before. I work alone, so I get help where I need it. That is not something I want
to hide.
## How I Actually Work
@@ -18,12 +20,13 @@ I plan the architecture, decide what gets built, and own every design decision.
- Read the Dalamud log output to verify behaviour
- Run security and privacy audits on anything that touches user data
One of the main reasons I use AI is consistency. I want the HellionChat code to match the style of the upstream Chat 2
codebase and stay readable for anyone who opens the repo, not just for me. Claude helps me catch when I am drifting from
upstream conventions or writing something that only makes sense in my own head.
One of the main reasons I use AI is consistency. I want the HellionChat code to match the style of
the upstream Chat 2 codebase and stay readable for anyone who opens the repo, not just for me.
Claude helps me catch when I am drifting from upstream conventions or writing something that only
makes sense in my own head.
The balance is shifting toward more hand-written work as I get more comfortable with Dalamud and plugin development in
general.
The balance is shifting toward more hand-written work as I get more comfortable with Dalamud and
plugin development in general.
## What AI Is Used For
@@ -34,23 +37,25 @@ general.
## What AI Is Not Used For
- **Visual assets.** Logos, icons, banners, and screenshots are human-drawn or taken from the running game.
- **Visual assets.** Logos, icons, banners, and screenshots are human-drawn or taken from the
running game.
- **German translations.** Written by me as a native speaker.
## What Is Where
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`, `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.
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`,
`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.
## If AI-Assisted Development Is a Dealbreaker for You
Fair enough. There are solid alternatives:
- [Chat 2](https://github.com/Infiziert90/ChatTwo), the upstream project HellionChat is built on
- [XIV Instant Messenger](https://github.com/NightmareXIV/XIVInstantMessenger), a different approach to FFXIV chat
- [XIV Instant Messenger](https://github.com/NightmareXIV/XIVInstantMessenger), a different approach
to FFXIV chat
Both are good projects. Use what fits you best.
@@ -71,4 +76,5 @@ Both are good projects. Use what fits you best.
## Contact
Questions about this disclosure: <https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues>
Questions about this disclosure:
<https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues>
+404 -345
View File
@@ -1,48 +1,48 @@
# Changelog — Hellion Chat
All user-facing changes to Hellion Chat. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
version numbers follow [Semantic Versioning](https://semver.org/).
All user-facing changes to Hellion Chat. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), version numbers follow
[Semantic Versioning](https://semver.org/).
Detailed release notes per version are available directly on the
[Gitea Release page](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases) and in the plugin
changelog block (`HellionChat/HellionChat.yaml``changelog:`). This file summarises releases as an overview and links
to the release pages for details.
[Gitea Release page](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases) and
in the plugin changelog block (`HellionChat/HellionChat.yaml``changelog:`). This file summarises
releases as an overview and links to the release pages for details.
---
## Hellion Chat 1.5.0 — DI Foundation and Service Refactor (2026-05-17)
Major architecture cycle. The plugin bootstrap moves to a generic-host DI container
(`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync. Service
logging migrates from a static `Plugin.LogProxy` locator to typed
`Microsoft.Extensions.Logging.ILogger<T>` via constructor injection, bridged over Dalamud's
`IPluginLog` by a custom `DalamudLogger` trio.
(`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync. Service logging
migrates from a static `Plugin.LogProxy` locator to typed `Microsoft.Extensions.Logging.ILogger<T>`
via constructor injection, bridged over Dalamud's `IPluginLog` by a custom `DalamudLogger` trio.
### Under the hood
- 18 instance-class services migrate to `ILogger<T>` via constructor injection across four
slices: data layer (`MessageStore`, `MessageManager`, `AutoTellTabsService`), IPC and
integrations (`HonorificService`, `IpcManager`, `TypingIpc`, `ExtraChat`, three
`GameFunctions` classes), UI window layer (`ChatLogWindow`, `DbViewer`, `Popout`, three
settings tabs), and root (`Commands`, `ThemeRegistry`, `PayloadHandler`).
- `Plugin.LogProxy` stays in place for the eight buckets ctor injection cannot reach:
static helpers (`EmoteCache`, `AutoTranslate`, `MemoryUtil`, `WrapperUtil`),
Dalamud-reflected types (`Configuration`), the `Message` data class, and instance classes
that only log from static methods (`FontManager`, one `GameFunctions` site).
- Plugin.cs finishes at 1012 lines — virtually identical to the pre-cycle 1013. The new
Phase-1 host build and `Plugin.X` bridge wiring trade out exactly the service and window
allocations that previously lived in `LoadAsync`.
- Cross-plugin baseline confirms no performance penalty against Chat 2: HellionChat
first-frame HITCH 77 ms median, Chat 2 74 ms median. Lightless and XIVInstantMessenger sit
around 7 ms by deferring their font-atlas build past `Finished loading` — that pattern is
the v1.5.1 follow-up item.
- 18 instance-class services migrate to `ILogger<T>` via constructor injection across four slices:
data layer (`MessageStore`, `MessageManager`, `AutoTellTabsService`), IPC and integrations
(`HonorificService`, `IpcManager`, `TypingIpc`, `ExtraChat`, three `GameFunctions` classes), UI
window layer (`ChatLogWindow`, `DbViewer`, `Popout`, three settings tabs), and root (`Commands`,
`ThemeRegistry`, `PayloadHandler`).
- `Plugin.LogProxy` stays in place for the eight buckets ctor injection cannot reach: static helpers
(`EmoteCache`, `AutoTranslate`, `MemoryUtil`, `WrapperUtil`), Dalamud-reflected types
(`Configuration`), the `Message` data class, and instance classes that only log from static
methods (`FontManager`, one `GameFunctions` site).
- Plugin.cs finishes at 1012 lines — virtually identical to the pre-cycle 1013. The new Phase-1 host
build and `Plugin.X` bridge wiring trade out exactly the service and window allocations that
previously lived in `LoadAsync`.
- Cross-plugin baseline confirms no performance penalty against Chat 2: HellionChat first-frame
HITCH 77 ms median, Chat 2 74 ms median. Lightless and XIVInstantMessenger sit around 7 ms by
deferring their font-atlas build past `Finished loading` — that pattern is the v1.5.1 follow-up
item.
### User-visible
- Slash-command insert fix: pasting a slash command into the chat input (Friend List
"/tell" action, plugin-driven inserts from Artisan, AllaganTools etc.) now replaces the
existing input instead of concatenating onto whatever the user was typing. Cherry-picked
from ChatTwo upstream `ee7768ac` with namespace adaptation.
- Slash-command insert fix: pasting a slash command into the chat input (Friend List "/tell" action,
plugin-driven inserts from Artisan, AllaganTools etc.) now replaces the existing input instead of
concatenating onto whatever the user was typing. Cherry-picked from ChatTwo upstream `ee7768ac`
with namespace adaptation.
Migration v17 stays (no schema bump).
@@ -52,23 +52,27 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## 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.
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<T>) modelled on Lightless.
- 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<T>) modelled on Lightless.
- Migration v17 stays (no schema bump).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
@@ -77,35 +81,39 @@ 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
~76 ms median — comfortably under Dalamud's 100 ms HITCH warning threshold. The remaining ~13 ms gap to ChatTwo
upstream (~63 ms median) is the cost of HellionChat-only features (sidebar tab view, custom status bar,
Honorific integration).
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median
down to ~76 ms median — comfortably under Dalamud's 100 ms HITCH warning threshold. The remaining
~13 ms gap to ChatTwo upstream (~63 ms median) is the cost of HellionChat-only features (sidebar tab
view, custom status bar, Honorific integration).
- First-frame defer: six non-essential rendering sections inside `ChatLogWindow` skip their first Draw and run
one frame later. Covered sections are the bottom status bar, channel-name SeString chunks, window bounds
check, v0.6.1 hint banner, autocomplete and input-preview calculation. At 60 fps the user sees those sections
~17 ms after plugin reload — invisible inside the ~2.5 s font-atlas build window every reload runs through
anyway. Frame 1 stays well under 100 ms too (~40 ms), so no secondary HITCH warning appears.
- Slash-command centralisation: `/hellion`, `/hellionView`, `/hellionSeString` and `/hellionDebugger` are now
registered during `LoadAsync` instead of inside the corresponding window constructors. The commands work
before their target window is opened the first time, and Dalamud's plugin-manager configuration / open
buttons (`UiBuilder.OpenConfigUi` / `OpenMainUi`) hang on the same path.
- Plugin-load profiling logs stay on: `MessageStore.Connect`, `MessageStore.Migrate`, `FilterAllTabs` and the
auto-translate warm-up timing log are now Information level rather than Debug. They serve as a tripwire so a
future regression past 100 ms shows up directly in `/xllog` without re-enabling Debug.
- First-frame defer: six non-essential rendering sections inside `ChatLogWindow` skip their first
Draw and run one frame later. Covered sections are the bottom status bar, channel-name SeString
chunks, window bounds check, v0.6.1 hint banner, autocomplete and input-preview calculation. At 60
fps the user sees those sections ~17 ms after plugin reload — invisible inside the ~2.5 s
font-atlas build window every reload runs through anyway. Frame 1 stays well under 100 ms too (~40
ms), so no secondary HITCH warning appears.
- Slash-command centralisation: `/hellion`, `/hellionView`, `/hellionSeString` and
`/hellionDebugger` are now registered during `LoadAsync` instead of inside the corresponding
window constructors. The commands work before their target window is opened the first time, and
Dalamud's plugin-manager configuration / open buttons (`UiBuilder.OpenConfigUi` / `OpenMainUi`)
hang on the same path.
- Plugin-load profiling logs stay on: `MessageStore.Connect`, `MessageStore.Migrate`,
`FilterAllTabs` and the auto-translate warm-up timing log are now Information level rather than
Debug. They serve as a tripwire so a future regression past 100 ms shows up directly in `/xllog`
without re-enabling Debug.
- 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.
(`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.
- Migration v17 stays (no schema bump).
- Internal: hypothesis-triage during the R2 cycle falsified three of the four candidate root causes
(font-atlas sync, theme-apply ABGR-cache init, multiple-window render). Actual cause is `DrawList` setup
cost distributed across ~10 ImGui sections inside ChatLogWindow (5-20 ms each). The six selective defers
above are the pragmatic fix — a clean structural rewrite would belong in the v1.5.x DI-container cycle.
(font-atlas sync, theme-apply ABGR-cache init, multiple-window render). Actual cause is `DrawList`
setup cost distributed across ~10 ImGui sections inside ChatLogWindow (5-20 ms each). The six
selective defers above are the pragmatic fix — a clean structural rewrite would belong in the
v1.5.x DI-container cycle.
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
@@ -113,28 +121,31 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search, ad-block foundation
investigation) plus three polish quick-wins.
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search,
ad-block foundation investigation) plus three polish quick-wins.
- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously on first run after
the update with a progress toast (UI stays responsive, the toggle is disabled until the build completes). The local
page-filter remains the default mode. Multi-word queries match as exact phrases; power users can opt into raw FTS5
`MATCH` syntax by wrapping their own double-quotes around the term.
- Custom theme files now auto-reload when edited while the theme is active. Save the JSON in your editor and the live
render picks up the change within a second — no need to re-click the theme in the picker. Disk-stat is throttled to
1 Hz so per-frame cost stays free.
- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously
on first run after the update with a progress toast (UI stays responsive, the toggle is disabled
until the build completes). The local page-filter remains the default mode. Multi-word queries
match as exact phrases; power users can opt into raw FTS5 `MATCH` syntax by wrapping their own
double-quotes around the term.
- Custom theme files now auto-reload when edited while the theme is active. Save the JSON in your
editor and the live render picks up the change within a second — no need to re-click the theme in
the picker. Disk-stat is throttled to 1 Hz so per-frame cost stays free.
- Retention sweep no longer blocks the framework thread. `Framework.Run(...).Wait()` is replaced by
`Framework.RunOnTick(...)`, which removes the ~194 ms hitch the sweep used to add per run.
- Status bar height is derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders
correctly at Windows display scaling above 100 %. Linux/Wayland default of 100 % is unaffected.
- Receive-suppressed-tells routing was investigated this cycle and **postponed to v1.5.x**. When other plugins suppress
tells via `CheckMessageHandled`, FFXIV's chat pipeline skips the `RaptureLogModule.AddMsgSourceEntry` path, which means
HellionChat's `ContentIdResolverHook` does not fire and tell-partner identification breaks for AutoTellTab routing.
The proper fix sits next to the planned ad-block hook layer (`RaptureLogModule.ShowMiniTalkPlayer` and friends) where
the same patch surface comes up anyway.
- Internal: storage form of `messages.Id` clarified (declared BLOB but Microsoft.Data.Sqlite stores Guid parameters as
TEXT). FTS bulk insert and `LoadByGuids` join now match the TEXT storage form on both sides. Migration v17 stays
(no schema bump).
- Status bar height is derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the
bar renders correctly at Windows display scaling above 100 %. Linux/Wayland default of 100 % is
unaffected.
- Receive-suppressed-tells routing was investigated this cycle and **postponed to v1.5.x**. When
other plugins suppress tells via `CheckMessageHandled`, FFXIV's chat pipeline skips the
`RaptureLogModule.AddMsgSourceEntry` path, which means HellionChat's `ContentIdResolverHook` does
not fire and tell-partner identification breaks for AutoTellTab routing. The proper fix sits next
to the planned ad-block hook layer (`RaptureLogModule.ShowMiniTalkPlayer` and friends) where the
same patch surface comes up anyway.
- Internal: storage form of `messages.Id` clarified (declared BLOB but Microsoft.Data.Sqlite stores
Guid parameters as TEXT). FTS bulk insert and `LoadByGuids` join now match the TEXT storage form
on both sides. Migration v17 stays (no schema bump).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
@@ -142,38 +153,43 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.7 — Backlog Cleanup and Mid-Features (2026-05-13)
Eighth 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, a configurable sidebar, plus a Settings-Save channel-preservation
fix surfaced during smoke testing.
Eighth 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, a configurable sidebar, plus a
Settings-Save channel-preservation fix surfaced during smoke testing.
- TempTell Pin: right-click a TempTell tab in the sidebar and choose "Pin Tab" / "Tab anpinnen". Pinned tabs survive
plugin reload and character logout, keep their conversation history (loaded on demand from the message store on
rehydrate), 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. The sidebar groups pinned tabs into their own section with a divider header
- Honorific glow outlines now render via an 8-direction DrawList pre-pass when the title carries a Glow colour. Opt-in
via **Settings → Integrations → Render glow outlines (Honorific)** (default off). Honorific's gradient surface
(`Color3`, `GradientColourSet`, `GradientAnimationStyle`) is parsed and stashed for a later cycle but renders as the
primary colour until then — the v1.4.7 DTO already mirrors all four extra fields so the JSON roundtrip doesn't
silent-drop them
- Sidebar width configurable in **Theme & Layout** (44160 px, default 44 stays icon-only). The icon button stretches
with the configured width so a widened sidebar looks intentional, not a 36 px icon floating in empty space
- `Configuration.UpdateFrom` now preserves the runtime `CurrentChannel` across the persistent-tab merge alongside
`Messages` and `LastSendUnread`. `TabSwitched` deep-clones the seeded channel from the previous tab instead of sharing
the same `UsedChannel` instance. Together these fix a regression where Settings-Save on a Party or Linkshell tab
popped the chat input back to `/tell <pinned-partner>` on the next interaction
- `Util/ImGuiUtil.cs` `DrawArrows` IconButton id uses `(id + 1).ToString()` with explicit parentheses instead of the
operator-precedence quirk `id + 1.ToString()` (which resolved to `id.ToString() + "1"`). Single live caller is
`Ui/DbViewer.cs:227` page-navigation
- 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. Production wrapper `DalamudPluginLogProxy` and Build-Suite `FakePluginLogProxy` mirror the full
`IPluginLog` surface (`Verbose`/`Debug`/`Information`/`Info`/`Warning`/`Error`/`Fatal`) with single-string,
- TempTell Pin: right-click a TempTell tab in the sidebar and choose "Pin Tab" / "Tab anpinnen".
Pinned tabs survive plugin reload and character logout, keep their conversation history (loaded on
demand from the message store on rehydrate), 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. The
sidebar groups pinned tabs into their own section with a divider header
- Honorific glow outlines now render via an 8-direction DrawList pre-pass when the title carries a
Glow colour. Opt-in via **Settings → Integrations → Render glow outlines (Honorific)** (default
off). Honorific's gradient surface (`Color3`, `GradientColourSet`, `GradientAnimationStyle`) is
parsed and stashed for a later cycle but renders as the primary colour until then — the v1.4.7 DTO
already mirrors all four extra fields so the JSON roundtrip doesn't silent-drop them
- Sidebar width configurable in **Theme & Layout** (44160 px, default 44 stays icon-only). The icon
button stretches with the configured width so a widened sidebar looks intentional, not a 36 px
icon floating in empty space
- `Configuration.UpdateFrom` now preserves the runtime `CurrentChannel` across the persistent-tab
merge alongside `Messages` and `LastSendUnread`. `TabSwitched` deep-clones the seeded channel from
the previous tab instead of sharing the same `UsedChannel` instance. Together these fix a
regression where Settings-Save on a Party or Linkshell tab popped the chat input back to
`/tell <pinned-partner>` on the next interaction
- `Util/ImGuiUtil.cs` `DrawArrows` IconButton id uses `(id + 1).ToString()` with explicit
parentheses instead of the operator-precedence quirk `id + 1.ToString()` (which resolved to
`id.ToString() + "1"`). Single live caller is `Ui/DbViewer.cs:227` page-navigation
- 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. Production wrapper `DalamudPluginLogProxy`
and Build-Suite `FakePluginLogProxy` mirror the full `IPluginLog` surface
(`Verbose`/`Debug`/`Information`/`Info`/`Warning`/`Error`/`Fatal`) with single-string,
`Exception+string`, and `params object[]` overloads
- Internal: TempTab counter switched from an `Interlocked` cached field to a derived `Tabs.Count(predicate)`. Pin-state
transitions (TryPin / Unpin / Promote) are cold-path and don't need lock-free reads; counter mutation surface dropped
from 5 to 0 sites. Build-Suite floor 688 → 710 (+22)
- Schema bump v16 → v17 is additive: new `Tab.IsPinned` bool, default false. Existing v16 configs load cleanly and get
their `Version` stamp bumped after the gate check
- Internal: TempTab counter switched from an `Interlocked` cached field to a derived
`Tabs.Count(predicate)`. Pin-state transitions (TryPin / Unpin / Promote) are cold-path and don't
need lock-free reads; counter mutation surface dropped from 5 to 0 sites. Build-Suite floor 688 →
710 (+22)
- Schema bump v16 → v17 is additive: new `Tab.IsPinned` bool, default false. Existing v16 configs
load cleanly and get their `Version` stamp bumped after the gate check
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
@@ -181,45 +197,51 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.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 from ChatTwo `f35b7d3`, and prepares the code for the v1.4.7 backlog cleanup.
Maintenance patch. No user-visible behaviour changes; tightens the development feedback loop, fixes
two upstream-inherited bugs from ChatTwo `f35b7d3`, and prepares the code for the v1.4.7 backlog
cleanup.
- `scripts/preflight.sh` gains Block E (`dotnet csharpier check`) and Block F (`markdownlint-cli2`) so reflow drift and
markdown violations are caught at the pre-push gate. `.markdownlint.json` adds `MD024 siblings_only` and disables
`MD036` so the bilingual forge-post bold-emphasis headings pass linting; the `.claude/` directory is excluded from the
scan
- `FontManager.AddFontWithFallback` catch-filter now covers `InvalidOperationException` and `ArgumentException` on top
of the existing IO triad. The warning log carries the exception type name, so the diagnostic path knows which class of
atlas-toolkit throw triggered the NotoSansCjkRegular fallback
- `BrandingLinks` (5 URLs) and `Integrations/IntegrationLinks` (2 URLs) validate themselves on first module load via
`[ModuleInitializer]` + a shared `UrlValidation.ValidateAll` helper. A malformed URL now throws
`InvalidOperationException` at plugin load with the source class and the broken URL in the message
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the native `Utf8String` when the
linkshell check rejects the channel. The validity check is now wrapped around the `ChangeChatChannel` call instead of
short-circuiting before `Dtor`. `ValidAnyLinkshell` is renamed to `IsChannelOrExistingLinkshell` and the
`ChatLogWindow` call-site follows the rename
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Tab.Clone` now deep-clones `UsedChannel` and `TellTarget`. The old
`CurrentChannel = CurrentChannel` was a reference copy, so PopOut and Temp tabs mutated each other's channel state
(incl. tell target). `TellTarget.From(t)` static factory is replaced with an instance `Clone()`; `UsedChannel.Clone()`
is new and runs deep-clone on both TellTarget references
- `ChatLogWindow` active-tab underline pill now scales with `ImGuiHelpers.GlobalScale` and rounds its DrawList
coordinates to physical pixels via `MathF.Round`, so the 2 px line stays crisp on 125 % and 150 % DPI setups instead
of bleeding into a sub-pixel blur
- `ImGuiUtil.IconButton` width parameter no longer subtracts HUD-scaled `CellPadding.X * 2` from the raw `int` width.
`ImGui.Button` handles its own frame padding internally, so the measured `buttonWidth` now passes through verbatim
(inspired-by upstream `f35b7d3`, but our two call-sites need the parameter, so the param itself stays)
- Internal: `HellionStyle` ChildBgAlpha threshold logic extracted to `HellionStyleHelpers.ResolveChildBgAlpha` with a
build-suite mirror test that pins the 0.999f cutoff. `Plugin.SaveConfig` clones only the temp-tab subset in the
pre-serialization snapshot instead of the full tab list. `SettingsOverview` caches `ImGui.GetWindowDrawList()` once
per frame and passes the pointer down to `DrawCard`
- Internal: `Dalamud.Utility.Util` static surface (`IsWine`, `OpenLink`) routed through a new `IPlatformUtil`
indirection. `MessageStore`'s `IsWine` probe is now reachable from the xUnit AppDomain via a `FakePlatformUtil`
fixture (full isolated MessageStore construction still pending — `Plugin.Log.Information` in `Migrate0` is a separate
Dalamud-static surface, slated for v1.4.7)
- Built-in themes: Crystal Nocturne (royal sapphire and electric magenta over obsidian, by CRYSTALLITE) replaces Moonlit
Bloom in the built-in roster. Users who had Moonlit Bloom selected fall back to the default Hellion Arctic on the
first plugin load; an existing custom JSON copy of Moonlit Bloom under `pluginConfigs/HellionChat/themes/` keeps
working unchanged
- `scripts/preflight.sh` gains Block E (`dotnet csharpier check`) and Block F (`markdownlint-cli2`)
so reflow drift and markdown violations are caught at the pre-push gate. `.markdownlint.json` adds
`MD024 siblings_only` and disables `MD036` so the bilingual forge-post bold-emphasis headings pass
linting; the `.claude/` directory is excluded from the scan
- `FontManager.AddFontWithFallback` catch-filter now covers `InvalidOperationException` and
`ArgumentException` on top of the existing IO triad. The warning log carries the exception type
name, so the diagnostic path knows which class of atlas-toolkit throw triggered the
NotoSansCjkRegular fallback
- `BrandingLinks` (5 URLs) and `Integrations/IntegrationLinks` (2 URLs) validate themselves on first
module load via `[ModuleInitializer]` + a shared `UrlValidation.ValidateAll` helper. A malformed
URL now throws `InvalidOperationException` at plugin load with the source class and the broken URL
in the message
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the native
`Utf8String` when the linkshell check rejects the channel. The validity check is now wrapped
around the `ChangeChatChannel` call instead of short-circuiting before `Dtor`. `ValidAnyLinkshell`
is renamed to `IsChannelOrExistingLinkshell` and the `ChatLogWindow` call-site follows the rename
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Tab.Clone` now deep-clones `UsedChannel` and
`TellTarget`. The old `CurrentChannel = CurrentChannel` was a reference copy, so PopOut and Temp
tabs mutated each other's channel state (incl. tell target). `TellTarget.From(t)` static factory
is replaced with an instance `Clone()`; `UsedChannel.Clone()` is new and runs deep-clone on both
TellTarget references
- `ChatLogWindow` active-tab underline pill now scales with `ImGuiHelpers.GlobalScale` and rounds
its DrawList coordinates to physical pixels via `MathF.Round`, so the 2 px line stays crisp on 125
% and 150 % DPI setups instead of bleeding into a sub-pixel blur
- `ImGuiUtil.IconButton` width parameter no longer subtracts HUD-scaled `CellPadding.X * 2` from the
raw `int` width. `ImGui.Button` handles its own frame padding internally, so the measured
`buttonWidth` now passes through verbatim (inspired-by upstream `f35b7d3`, but our two call-sites
need the parameter, so the param itself stays)
- Internal: `HellionStyle` ChildBgAlpha threshold logic extracted to
`HellionStyleHelpers.ResolveChildBgAlpha` with a build-suite mirror test that pins the 0.999f
cutoff. `Plugin.SaveConfig` clones only the temp-tab subset in the pre-serialization snapshot
instead of the full tab list. `SettingsOverview` caches `ImGui.GetWindowDrawList()` once per frame
and passes the pointer down to `DrawCard`
- Internal: `Dalamud.Utility.Util` static surface (`IsWine`, `OpenLink`) routed through a new
`IPlatformUtil` indirection. `MessageStore`'s `IsWine` probe is now reachable from the xUnit
AppDomain via a `FakePlatformUtil` fixture (full isolated MessageStore construction still pending
`Plugin.Log.Information` in `Migrate0` is a separate Dalamud-static surface, slated for v1.4.7)
- Built-in themes: Crystal Nocturne (royal sapphire and electric magenta over obsidian, by
CRYSTALLITE) replaces Moonlit Bloom in the built-in roster. Users who had Moonlit Bloom selected
fall back to the default Hellion Arctic on the first plugin load; an existing custom JSON copy of
Moonlit Bloom under `pluginConfigs/HellionChat/themes/` keeps working unchanged
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -229,26 +251,31 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.5 — UX and Robustness (2026-05-12)
Sixth sub-patch of the v1.4.x polish-sweep series. User-visible robustness fixes plus two doc/test polish items from the
audit backlog. No schema bump, no migration.
Sixth sub-patch of the v1.4.x polish-sweep series. User-visible robustness fixes plus two doc/test
polish items from the audit backlog. No schema bump, no migration.
- `ChatLogWindow.Draw` now surfaces a one-shot warning notification when the draw path throws. The stack trace still
goes to `/xllog` via `Plugin.Log.Error`; the notification is suppressed for the rest of the plugin session so a
recurring failure can't spam the notification stack frame-by-frame. Pattern-match to the existing `Plugin.cs:505-516`
migration-blocker notification
- `FirstRunWizard` splits accept from close. `OnClose` no longer silently sets `FirstRunCompleted`, so closing the X
leaves the wizard pending and it reopens on the next plugin load. A new footer "Later — keep defaults" button is the
explicit path to dismiss without picking a profile. Bilingual strings (EN + DE) plus a tooltip
- `InputHistoryService.Reset` is wired into `Plugin.DisposeAsync` alongside the existing pure-memory cleanups. Static
state used to survive a plugin reload — the next load now starts with an empty history
- `FontManager.GetHellionFontBytes` becomes `TryGetHellionFontBytes` with a nullable return. On miss (broken csproj,
hand-rolled dev build) the caller falls back to the system-font path that `UseHellionFont=false` already uses, plus a
`Plugin.Log.Warning`. The whole UiBuilder no longer throws if the embedded font resource is absent
- `Plugin.cs:167-168` gets a 4-line reasoning comment around the session-only `RemoveAll(IsTempTab)`: tells are usually
privacy-filtered, resurrecting an empty crashed-session tab would trigger DB reconstruction on the next load.
`TempTabCounter.InitFromList` mirrors the post-strip semantic in the Build-Suite with a pinning test
- `StatusBar.cs` drops the version slot when the chat window's content width minus the version text is below 200 px. The
right-aligned version used to clip into the four left-side slots in narrow windows
- `ChatLogWindow.Draw` now surfaces a one-shot warning notification when the draw path throws. The
stack trace still goes to `/xllog` via `Plugin.Log.Error`; the notification is suppressed for the
rest of the plugin session so a recurring failure can't spam the notification stack
frame-by-frame. Pattern-match to the existing `Plugin.cs:505-516` migration-blocker notification
- `FirstRunWizard` splits accept from close. `OnClose` no longer silently sets `FirstRunCompleted`,
so closing the X leaves the wizard pending and it reopens on the next plugin load. A new footer
"Later — keep defaults" button is the explicit path to dismiss without picking a profile.
Bilingual strings (EN + DE) plus a tooltip
- `InputHistoryService.Reset` is wired into `Plugin.DisposeAsync` alongside the existing pure-memory
cleanups. Static state used to survive a plugin reload — the next load now starts with an empty
history
- `FontManager.GetHellionFontBytes` becomes `TryGetHellionFontBytes` with a nullable return. On miss
(broken csproj, hand-rolled dev build) the caller falls back to the system-font path that
`UseHellionFont=false` already uses, plus a `Plugin.Log.Warning`. The whole UiBuilder no longer
throws if the embedded font resource is absent
- `Plugin.cs:167-168` gets a 4-line reasoning comment around the session-only
`RemoveAll(IsTempTab)`: tells are usually privacy-filtered, resurrecting an empty crashed-session
tab would trigger DB reconstruction on the next load. `TempTabCounter.InitFromList` mirrors the
post-strip semantic in the Build-Suite with a pinning test
- `StatusBar.cs` drops the version slot when the chat window's content width minus the version text
is below 200 px. The right-aligned version used to clip into the four left-side slots in narrow
windows
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -258,29 +285,32 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.4 — Threading and IPC Safety Polish (2026-05-12)
Fifth sub-patch of the v1.4.x polish-sweep series. Threading assumptions are documented per-method, a hot-path lock
falls away in `AutoTellTabsService`, IPC-cleanup failures become visible, and the privacy filter now speaks up when an
unknown ChatType shows up.
Fifth sub-patch of the v1.4.x polish-sweep series. Threading assumptions are documented per-method,
a hot-path lock falls away in `AutoTellTabsService`, IPC-cleanup failures become visible, and the
privacy filter now speaks up when an unknown ChatType shows up.
- `AutoTellTabsService.ActiveTempTabCount` switches from a lock-protected LINQ `Count` to an `Interlocked` counter kept
in sync with `Config.Tabs` from inside the existing mutation paths. `Initialize()` seeds the counter from the
persisted Tabs list, and `SaveConfig`'s snapshot-restore path calls a new `ResyncTempTabCounter()` so the mid-step
`RemoveAll` doesn't leave the counter drifting. Pure-helper test mirror lives in the Build-Suite repo
- `HonorificService` per-method threading banners replace the block comment at the bottom of the file. Each IPC callback
(`TryInitialPull`, `OnTitleChanged`, `OnReady`, `OnDisposing`, `TryUnsubscribe`) and the `CurrentTitle` field carry a
one-line `// Thread:` annotation so the framework-thread invariant is visible at the call site
- `TryUnsubscribe` log-level upgraded from `Debug` to `Warning`. A silent unsubscribe failure leaks a live subscription
across plugin reloads, which is exactly the kind of issue that should not be at Debug
- `AutoTranslate.PreloadCache` thread now has `IsBackground = true` and a thread name. Without `IsBackground` the warmup
blocks plugin unload (typically 100-300 ms). Pattern-match to `MessageManager` (F6.1) and `Plugin.RetentionSweep`
(F9.3), both since v1.4.0
- `Configuration.IsAllowedForStorage` adds a one-line `Plugin.Log.Warning` for the first occurrence of any ChatType that
isn't in `PrivacyPersistChannels`. Dedup via a `NonSerialized` `HashSet<ChatType>`, so the warning fires once per
runtime — not once per frame, not once per install. Failsafe routing through `PrivacyPersistUnknownChannels` is
unchanged
- `PrivacyPersistUnknownChannels` field default flipped from `false` to `true` for new installs via a constant in
`PrivacyDefaults`. Existing configs keep their explicit choice — the deserializer overrides the initializer. No schema
bump, no migration, no first-run banner
- `AutoTellTabsService.ActiveTempTabCount` switches from a lock-protected LINQ `Count` to an
`Interlocked` counter kept in sync with `Config.Tabs` from inside the existing mutation paths.
`Initialize()` seeds the counter from the persisted Tabs list, and `SaveConfig`'s snapshot-restore
path calls a new `ResyncTempTabCounter()` so the mid-step `RemoveAll` doesn't leave the counter
drifting. Pure-helper test mirror lives in the Build-Suite repo
- `HonorificService` per-method threading banners replace the block comment at the bottom of the
file. Each IPC callback (`TryInitialPull`, `OnTitleChanged`, `OnReady`, `OnDisposing`,
`TryUnsubscribe`) and the `CurrentTitle` field carry a one-line `// Thread:` annotation so the
framework-thread invariant is visible at the call site
- `TryUnsubscribe` log-level upgraded from `Debug` to `Warning`. A silent unsubscribe failure leaks
a live subscription across plugin reloads, which is exactly the kind of issue that should not be
at Debug
- `AutoTranslate.PreloadCache` thread now has `IsBackground = true` and a thread name. Without
`IsBackground` the warmup blocks plugin unload (typically 100-300 ms). Pattern-match to
`MessageManager` (F6.1) and `Plugin.RetentionSweep` (F9.3), both since v1.4.0
- `Configuration.IsAllowedForStorage` adds a one-line `Plugin.Log.Warning` for the first occurrence
of any ChatType that isn't in `PrivacyPersistChannels`. Dedup via a `NonSerialized`
`HashSet<ChatType>`, so the warning fires once per runtime — not once per frame, not once per
install. Failsafe routing through `PrivacyPersistUnknownChannels` is unchanged
- `PrivacyPersistUnknownChannels` field default flipped from `false` to `true` for new installs via
a constant in `PrivacyDefaults`. Existing configs keep their explicit choice — the deserializer
overrides the initializer. No schema bump, no migration, no first-run banner
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -290,22 +320,26 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin` API. The constructor now does only the bootstrap-essentials
(config load, language init, conflict detection); migrations, service allocations, window construction and hook
subscription move to `LoadAsync`. Dalamud can keep its UI responsive while the heavy work runs.
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin` API. The constructor now does only the
bootstrap-essentials (config load, language init, conflict detection); migrations, service
allocations, window construction and hook subscription move to `LoadAsync`. Dalamud can keep its UI
responsive while the heavy work runs.
- `IAsyncDalamudPlugin` two-phase load with per-line `CaptureFailure` in `DisposeAsync` (mirrors LightlessSync's
pattern); idempotency guard protects against reload races
- Schema-gate replaces the v9 → v16 migration chain. Configs on schema v16+ load directly; older configs trigger an
"install v1.4.2 first" error so the historic migration path stays intact
- `AutoTranslate.PreloadCache` moved off the load path. First use may have a sub-second hitch instead of every-load; the
upstream chose differently, we accept first-use latency
- `FontManager.BuildFonts` is called sync at the start of `LoadAsync`; Dalamud rebuilds the font atlas on its own
pipeline so the custom Hellion-Exo2 font appears with a brief font-pop after load (matches ChatTwo's behaviour)
- Custom-repo URL moved to `gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat`. GitHub repo stays as a frozen
v1.4.2 snapshot; new releases ship from Gitea. Existing testers need to update the custom-repo URL once
- Plugin-load time in this release sits at ~3.7 s median (5 reloads), comparable to v1.4.2. Async migration is
foundational for v1.4.4 Lazy-Init optimisations rather than an immediate user-perceived win
- `IAsyncDalamudPlugin` two-phase load with per-line `CaptureFailure` in `DisposeAsync` (mirrors
LightlessSync's pattern); idempotency guard protects against reload races
- Schema-gate replaces the v9 → v16 migration chain. Configs on schema v16+ load directly; older
configs trigger an "install v1.4.2 first" error so the historic migration path stays intact
- `AutoTranslate.PreloadCache` moved off the load path. First use may have a sub-second hitch
instead of every-load; the upstream chose differently, we accept first-use latency
- `FontManager.BuildFonts` is called sync at the start of `LoadAsync`; Dalamud rebuilds the font
atlas on its own pipeline so the custom Hellion-Exo2 font appears with a brief font-pop after load
(matches ChatTwo's behaviour)
- Custom-repo URL moved to `gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat`. GitHub repo
stays as a frozen v1.4.2 snapshot; new releases ship from Gitea. Existing testers need to update
the custom-repo URL once
- Plugin-load time in this release sits at ~3.7 s median (5 reloads), comparable to v1.4.2. Async
migration is foundational for v1.4.4 Lazy-Init optimisations rather than an immediate
user-perceived win
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -315,17 +349,21 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations from the chat-log render path eliminated.
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations from the chat-log render
path eliminated.
- `DrawMessages` card-mode hoists `theme`/`drawList`/`winLeft`/`winRight`/`borderColorAbgr` out of the per-message loop.
About 500 redundant calls per frame at 100 visible messages, multiplied by every pop-out window
- Auto-tell tab tint and icon use a per-tab cache. Hash computation and string allocation only happen when the tell
target name or world drifts. `AutoTellTabTint` stays a pure hash helper; cache lives in a thin `TabTintCache` wrapper
- Status bar gates its tab aggregation behind the same one-second cache it already used for the format strings. LINQ
`Sum` and `Count` replaced with a single `foreach` pass that runs on roughly 1 % of frames
- `DrawMessages` card-mode hoists `theme`/`drawList`/`winLeft`/`winRight`/`borderColorAbgr` out of
the per-message loop. About 500 redundant calls per frame at 100 visible messages, multiplied by
every pop-out window
- Auto-tell tab tint and icon use a per-tab cache. Hash computation and string allocation only
happen when the tell target name or world drifts. `AutoTellTabTint` stays a pure hash helper;
cache lives in a thin `TabTintCache` wrapper
- Status bar gates its tab aggregation behind the same one-second cache it already used for the
format strings. LINQ `Sum` and `Count` replaced with a single `foreach` pass that runs on roughly
1 % of frames
Realistic frame-time recovery: 2-5 % in typical scenes, more on pop-out-heavy setups because the card-border hoist
scales per window.
Realistic frame-time recovery: 2-5 % in typical scenes, more on pop-out-heavy setups because the
card-border hoist scales per window.
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -335,23 +373,24 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.1 — Theme Engine Performance
Second sub-patch of the v1.4.x Polish Sweep series. Heap pressure from the theme engine's per-frame render path removed,
plus a tenth built-in theme and hardening for the custom-theme hot-reload.
Second sub-patch of the v1.4.x Polish Sweep series. Heap pressure from the theme engine's per-frame
render path removed, plus a tenth built-in theme and hardening for the custom-theme hot-reload.
- Theme records carry a pre-computed ABGR-packed cache for every color slot; cache is filled when the theme is
registered and refreshed defensively on every `Switch()`
- `HellionStyle.PushGlobal` reads ABGR values from the cache instead of calling `ColourUtil.RgbaToAbgr` per slot per
frame; ~13 % render-time recovery measured in typical scenes (plan estimate was 26 %, real ~1015 %)
- `ThemeRegistry` custom-theme reload distinguishes a recoverable file lock (editor mid-save) from a permanent IO
failure; locked themes keep their last-known-good snapshot and retry on the next lookup instead of dropping out of the
picker
- New built-in: **Synthwave Sunset** — Hot Magenta + Cyan on midnight violet, 80s neon-grid vibes; tenth theme in the
picker
- Author credits refreshed: brand themes are credited as "Hellion Forge"; **Mint Grove** and **Forge Merchantman** now
credited to **Carla Beleandis** as a community thanks
- Theme records carry a pre-computed ABGR-packed cache for every color slot; cache is filled when
the theme is registered and refreshed defensively on every `Switch()`
- `HellionStyle.PushGlobal` reads ABGR values from the cache instead of calling
`ColourUtil.RgbaToAbgr` per slot per frame; ~13 % render-time recovery measured in typical scenes
(plan estimate was 26 %, real ~1015 %)
- `ThemeRegistry` custom-theme reload distinguishes a recoverable file lock (editor mid-save) from a
permanent IO failure; locked themes keep their last-known-good snapshot and retry on the next
lookup instead of dropping out of the picker
- New built-in: **Synthwave Sunset** — Hot Magenta + Cyan on midnight violet, 80s neon-grid vibes;
tenth theme in the picker
- Author credits refreshed: brand themes are credited as "Hellion Forge"; **Mint Grove** and **Forge
Merchantman** now credited to **Carla Beleandis** as a community thanks
No schema bump, no user-visible behaviour change other than smoother frames on GC-sensitive setups and one additional
colour option.
No schema bump, no user-visible behaviour change other than smoother frames on GC-sensitive setups
and one additional colour option.
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -361,20 +400,20 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.4.0 — Critical Lifecycle Fixes
First sub-patch of the v1.4.x Polish Sweep series. Seven known lifecycle and race bugs eliminated before any performance
refactor sits on top.
First sub-patch of the v1.4.x Polish Sweep series. Seven known lifecycle and race bugs eliminated
before any performance refactor sits on top.
- MessageStore disposal no longer triggers GC.Collect globally; Pooling=false on the SQLite connection means there's
nothing left to clean up by hand
- PendingMessage and RetentionSweep worker threads are explicitly marked IsBackground=true so the plugin domain can
unload during XIVLauncher reload without waiting for them
- EmoteCache image and gif loaders moved from async-void to async Task with a shared task tracker, draining on Dispose
so an in-flight load can no longer write to a disposed EmoteImages entry
- MessageStore disposal no longer triggers GC.Collect globally; Pooling=false on the SQLite
connection means there's nothing left to clean up by hand
- PendingMessage and RetentionSweep worker threads are explicitly marked IsBackground=true so the
plugin domain can unload during XIVLauncher reload without waiting for them
- EmoteCache image and gif loaders moved from async-void to async Task with a shared task tracker,
draining on Dispose so an in-flight load can no longer write to a disposed EmoteImages entry
- DisposeAsync 10s timeout now warns loudly instead of silently leaving the worker behind
- Plugin.Dispose flushes any pending DeferredSaveFrames before tearing services down, so settings changes made in the
last few frames before disable are no longer lost
- The v13→v14 config migration now reads the pre-v13 backup and carries HellionThemeWindowOpacity into the new
WindowOpacity field instead of falling back to the default 0.85
- Plugin.Dispose flushes any pending DeferredSaveFrames before tearing services down, so settings
changes made in the last few frames before disable are no longer lost
- The v13→v14 config migration now reads the pre-v13 backup and carries HellionThemeWindowOpacity
into the new WindowOpacity field instead of falling back to the default 0.85
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
@@ -384,14 +423,14 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
## Hellion Chat 1.3.0 — Plugin Integrations: Honorific
First step on the plugin-integration roadmap. HellionChat now listens to Honorific and shows your custom title in the
chat header. The slot auto-hides when Honorific is not installed, when no custom title is active, or when you are using
the original FFXIV title.
First step on the plugin-integration roadmap. HellionChat now listens to Honorific and shows your
custom title in the chat header. The slot auto-hides when Honorific is not installed, when no custom
title is active, or when you are using the original FFXIV title.
- New "Integrations" settings tab
- Honorific integration with auto-detection and live updates
- "Coming soon" preview of the next five planned integrations: context menu actions, smart notifications, RP status
block, ExtraChat channels, and quick DM compose
- "Coming soon" preview of the next five planned integrations: context menu actions, smart
notifications, RP status block, ExtraChat channels, and quick DM compose
- Maintainer attribution buttons for Honorific repo and Caraxi
- New service-class pattern under HellionChat/Integrations/
@@ -406,20 +445,22 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
### Added
- Four new built-in themes:
- **Night Blue** — Royal Blue on deep marine, cool tech-dashboard mood
- **Indigo Violet** — Royal Violet on deep indigo with a turquoise-mint counter (aurora glitter feel)
- **Forge Merchantman** — Patina bronze on workshop slate with warm amber counter (Hellion Forge identity)
- **Hellion Spectrum** — Deuteran/Protan-safe channel colours using Wong/Okabe-Ito palette tones; channel identity
(Tell pink, Yell yellow, Shout orange, Party blue, FC green) preserved while keeping every channel separable under
red-green colour vision deficiency
- **Night Blue** — Royal Blue on deep marine, cool tech-dashboard mood
- **Indigo Violet** — Royal Violet on deep indigo with a turquoise-mint counter (aurora glitter
feel)
- **Forge Merchantman** — Patina bronze on workshop slate with warm amber counter (Hellion Forge
identity)
- **Hellion Spectrum** — Deuteran/Protan-safe channel colours using Wong/Okabe-Ito palette tones;
channel identity (Tell pink, Yell yellow, Shout orange, Party blue, FC green) preserved while
keeping every channel separable under red-green colour vision deficiency
- Built-in theme catalogue grown from five to nine
### Notes
- No engine changes, no settings touched, no migration
- Default theme unchanged (Hellion Arctic). Existing custom themes keep working.
- Hellion Spectrum covers the ~99 % of CVD cases that are red-green; a Tritan-safe variant could follow in a later cycle
if there is demand.
- Hellion Spectrum covers the ~99 % of CVD cases that are red-green; a Tritan-safe variant could
follow in a later cycle if there is demand.
---
@@ -427,21 +468,23 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
### Changed
- Settings cards re-sorted thematically: 9 cards remain, each card has one clear job and a one-line subtitle.
- **Theme & Layout** (new) collects the theme picker, window frame style (title bar, sidebar, hide button, pop-out title
bar) and the timestamp style options.
- Settings cards re-sorted thematically: 9 cards remain, each card has one clear job and a one-line
subtitle.
- **Theme & Layout** (new) collects the theme picker, window frame style (title bar, sidebar, hide
button, pop-out title bar) and the timestamp style options.
- **Fonts & Colours** (new) houses font choice, font size and per-channel chat colours.
- **Data Management** (new) collects retention windows, cleanup, export, the database viewer and the shift-click
advanced tools.
- **Data Management** (new) collects retention windows, cleanup, export, the database viewer and the
shift-click advanced tools.
- **Privacy** is now focused on the privacy filter alone.
- **Chat** absorbs the Auto-Tell-Tabs history-preload slider that used to live under Privacy.
- **General** groups the keybind mode under Input.
### Removed
- Legacy "Style override" option and the unused style-name field — both obsolete since the v1.1.0 themes engine.
- Legacy `WindowAlpha` slider — if you had it set, the value is auto-migrated to Theme & Layout → Window Style → Window
Transparency.
- Legacy "Style override" option and the unused style-name field — both obsolete since the v1.1.0
themes engine.
- Legacy `WindowAlpha` slider — if you had it set, the value is auto-migrated to Theme & Layout →
Window Style → Window Transparency.
- Unused `ShowThemeQuickPicker` schema field.
### Migration
@@ -456,82 +499,93 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
### Added
- Sidebar tab modernization: icon-only at fixed 44 px, tooltip on hover, vertical accent pill for active tab
- Sidebar tab modernization: icon-only at fixed 44 px, tooltip on hover, vertical accent pill for
active tab
- Top tabs: accent underline pill replaces background fill on active tab
- Per-tab custom icons in Settings → Tabs (15-glyph FontAwesome picker)
- Bottom status bar (22 px): channel indicator, privacy badge, counters, tells, version — updates 1×/sec
- Bottom status bar (22 px): channel indicator, privacy badge, counters, tells, version — updates
1×/sec
- Card rows as default message render: sender header in channel color, subtle border between cards
- Compact-Density toggle in Appearance: switches back to single-line `[HH:mm] Sender: Text` layout
- Auto-Tell tabs: per-partner hashed icon (7-glyph pool: envelope/star/heart/bell/bookmark/flag/fire) plus hashed color
(12-color palette) — 84 distinct icon+color combinations
- Unread indicator: pulsing red dot in the top-right corner of any sidebar tab icon with unread messages, 2-second
sine-wave pulse, respects `Configuration.ReduceMotion`
- Auto-Tell tabs: per-partner hashed icon (7-glyph pool:
envelope/star/heart/bell/bookmark/flag/fire) plus hashed color (12-color palette) — 84 distinct
icon+color combinations
- Unread indicator: pulsing red dot in the top-right corner of any sidebar tab icon with unread
messages, 2-second sine-wave pulse, respects `Configuration.ReduceMotion`
### Changed
- Migration v14 → v15: deprecated Configuration fields `HellionThemeEnabled` and `HellionThemeWindowOpacity` removed
- Appearance settings cleaned: legacy theme-engine bindings replaced by Themes tab (introduced in v1.1.0)
- Migration v14 → v15: deprecated Configuration fields `HellionThemeEnabled` and
`HellionThemeWindowOpacity` removed
- Appearance settings cleaned: legacy theme-engine bindings replaced by Themes tab (introduced in
v1.1.0)
### Fixed
- Settings save no longer wipes chat history by default — the heavy `ClearAllTabs + FilterAllTabsAsync` cycle now only
runs when a filter-relevant setting actually changed (Privacy filter, persisted channels, per-tab channel selection).
Cosmetic changes keep the in-session chat intact
- Settings save no longer wipes chat history by default — the heavy
`ClearAllTabs + FilterAllTabsAsync` cycle now only runs when a filter-relevant setting actually
changed (Privacy filter, persisted channels, per-tab channel selection). Cosmetic changes keep the
in-session chat intact
- Identifier-based `MessageList` restore in `Configuration.UpdateFrom` plus TempTab skip in
`ClearAllTabs`/`FilterAllTabs` ensure persistent tabs and Auto-Tell tabs both survive the save
- Sidebar buttons now align vertically with the first message row (top padding mirrors the chat header toolbar height)
- Sidebar buttons now align vertically with the first message row (top padding mirrors the chat
header toolbar height)
- Sidebar child window no longer paints the top padding area with its frame background
- Status bar version slot (`vX.Y.Z · Hellion`) no longer clips its rightmost character
### Notes
- Polish phase (animations, theme crossfade, header quick-picker) follows in v1.3.0
- Top-Tab icon prefixes were considered but dropped: Dalamud's default font atlas does not include FontAwesome
codepoints, so mixed-font in a single TabItem label renders as tofu. Underline pill alone is the v1.2.0 visual
treatment for top tabs. Resolution would require Font-Atlas merge at FontManager level — out of scope.
- Top-Tab icon prefixes were considered but dropped: Dalamud's default font atlas does not include
FontAwesome codepoints, so mixed-font in a single TabItem label renders as tofu. Underline pill
alone is the v1.2.0 visual treatment for top tabs. Resolution would require Font-Atlas merge at
FontManager level — out of scope.
---
## [1.1.0] — 2026-05-05 — Theme Foundation
First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom themes via JSON, settings card grid.
First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom themes via JSON,
settings card grid.
### Added
- **Theme engine** with five built-in themes: Hellion Arctic (default), Chat 2 Classic, Event Horizon, Moonlit Bloom,
Mint Grove.
- **Settings → Themes** with mini mockup preview per theme. Clicking a card instantly switches the entire plugin (chat,
settings, pop-outs).
- **Custom themes via JSON** in `pluginConfigs/HellionChat/themes/`. On first start, `example-theme.json` is placed
there as a template.
- **Optional theme chat channel colours**: themes can ship their own channel colours. On switch, a banner appears with
_Apply / Keep current_ — never applied automatically.
- **Settings card grid**: new overview on open, clicking a card navigates into the section's detail view. Breadcrumb +
ESC navigate back.
- **Theme engine** with five built-in themes: Hellion Arctic (default), Chat 2 Classic, Event
Horizon, Moonlit Bloom, Mint Grove.
- **Settings → Themes** with mini mockup preview per theme. Clicking a card instantly switches the
entire plugin (chat, settings, pop-outs).
- **Custom themes via JSON** in `pluginConfigs/HellionChat/themes/`. On first start,
`example-theme.json` is placed there as a template.
- **Optional theme chat channel colours**: themes can ship their own channel colours. On switch, a
banner appears with _Apply / Keep current_ — never applied automatically.
- **Settings card grid**: new overview on open, clicking a card navigates into the section's detail
view. Breadcrumb + ESC navigate back.
- **`docs/THEME-AUTHORING.md`** as a guide for writing custom themes, with Hellion Forge branding.
### Changed
- **Plugin icon** updated to Hellion Forge hammer (previously a ChatTwo derivative).
- **Settings detail view** uses the full width — the second tab list on the left is gone because the card overview
handles navigation.
- **`HellionStyle.PushGlobal`** is now theme-driven (`PushGlobal(theme, opacity)`) instead of const-palette-driven.
- **Configuration v13 → v14**: all users land on `hellion-arctic`. Those who prefer the upstream look can select
`chat2-classic` in Settings → Themes.
- **Settings detail view** uses the full width — the second tab list on the left is gone because the
card overview handles navigation.
- **`HellionStyle.PushGlobal`** is now theme-driven (`PushGlobal(theme, opacity)`) instead of
const-palette-driven.
- **Configuration v13 → v14**: all users land on `hellion-arctic`. Those who prefer the upstream
look can select `chat2-classic` in Settings → Themes.
### Deprecated
- `Configuration.HellionThemeEnabled` and `HellionThemeWindowOpacity` remain readable for one release as a safety net
but are no longer evaluated. Removal planned for v1.2.0.
- `Configuration.HellionThemeEnabled` and `HellionThemeWindowOpacity` remain readable for one
release as a safety net but are no longer evaluated. Removal planned for v1.2.0.
### Security
- Custom theme JSON loader validates `schemaVersion`, required fields and hex format. Invalid themes are skipped with a
warning; the plugin continues loading with built-ins.
- Custom theme JSON loader validates `schemaVersion`, required fields and hex format. Invalid themes
are skipped with a warning; the plugin continues loading with built-ins.
### Internal
- 51 local unit tests (theme records, registry, JSON round-trip, sanity per built-in theme). Tests are gitignored.
- 51 local unit tests (theme records, registry, JSON round-trip, sanity per built-in theme). Tests
are gitignored.
---
@@ -539,18 +593,20 @@ First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom th
Four small polish items from the backlog bundled together:
- **Hide on New Game+ menu**: optional global toggle that hides Hellion Chat (and all other plugin windows such as
Settings, DB Viewer, pop-outs) while the NG+ menu is open. Settings → Window → Frame, default off. Skips the entire
`WindowSystem.Draw()` path analogous to the existing LoadingScreens pattern.
- **Channel selector colouring**: optional tinting of the channel-select button (comment icon) next to the input field
in the current channel colour. Settings → Appearance → Chat Colours, default on. Consistent with the existing input
text colouring; ExtraChat override is carried over.
- **(De)buff icon aspect-ratio fix**: `PayloadHandler.InlineIcon` was squashing all hover icons to 32×32. Status icons
with non-square dimensions (debuffs with an arrow indicator) are now shrunk aspect-preserving. Standalone float-math
implementation with zero-size guard instead of a cherry-pick from the open ChatTwo PR #157 (which had an int-division
trap).
- **HideState logging sweep**: all HideState transitions (Battle/Cutscene/User/Override plus pop-out mirroring) log at
verbose level. Off by default; enable via `/xllog set HellionChat verbose` for bug-report diagnostics.
- **Hide on New Game+ menu**: optional global toggle that hides Hellion Chat (and all other plugin
windows such as Settings, DB Viewer, pop-outs) while the NG+ menu is open. Settings → Window →
Frame, default off. Skips the entire `WindowSystem.Draw()` path analogous to the existing
LoadingScreens pattern.
- **Channel selector colouring**: optional tinting of the channel-select button (comment icon) next
to the input field in the current channel colour. Settings → Appearance → Chat Colours, default
on. Consistent with the existing input text colouring; ExtraChat override is carried over.
- **(De)buff icon aspect-ratio fix**: `PayloadHandler.InlineIcon` was squashing all hover icons to
32×32. Status icons with non-square dimensions (debuffs with an arrow indicator) are now shrunk
aspect-preserving. Standalone float-math implementation with zero-size guard instead of a
cherry-pick from the open ChatTwo PR #157 (which had an int-division trap).
- **HideState logging sweep**: all HideState transitions (Battle/Cutscene/User/Override plus pop-out
mirroring) log at verbose level. Off by default; enable via `/xllog set HellionChat verbose` for
bug-report diagnostics.
[Release Notes 1.0.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.3)
@@ -558,14 +614,14 @@ Four small polish items from the backlog bundled together:
## [1.0.1] — 2026-05-04 — Window Position Recovery
Fixes an off-screen-window scenario the user could end up in after a monitor disconnect or display layout change between
sessions. An automatic one-shot bounds check on the first draw after plugin load snaps the window back into the visible
viewport, and a new "Reset Window Position" button in Settings → Window → Frame serves as the manual escape hatch for
edge cases.
Fixes an off-screen-window scenario the user could end up in after a monitor disconnect or display
layout change between sessions. An automatic one-shot bounds check on the first draw after plugin
load snaps the window back into the visible viewport, and a new "Reset Window Position" button in
Settings → Window → Frame serves as the manual escape hatch for edge cases.
Bundled housekeeping since v1.0.0: documentation restructured into `docs/`, stale ChatTwo/\* paths in repo configs
cleaned up, Pidgin parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps for `actions/setup-dotnet` (4 → 5)
and `github/codeql-action` (3 → 4).
Bundled housekeeping since v1.0.0: documentation restructured into `docs/`, stale ChatTwo/\* paths
in repo configs cleaned up, Pidgin parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps
for `actions/setup-dotnet` (4 → 5) and `github/codeql-action` (3 → 4).
[Release Notes 1.0.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.1)
@@ -573,11 +629,11 @@ and `github/codeql-action` (3 → 4).
## [1.0.0] — 2026-05-03 — Standalone Major Release
First fully independent release. Code namespace, IPC channels and source tree structure consolidated under
`HellionChat.*`. Plugin refuses to start alongside an active upstream Chat 2 (bilingual conflict message). SQLite native
pinned to 3.50.3 (CVE-2025-6965, CVE-2025-7709). Tab layout default for new installs and users on config version 12 or
older restructured (5 thematic tabs instead of 6+ kitchen-sink). Sweep of critical and major findings from the codebase
audit incorporated.
First fully independent release. Code namespace, IPC channels and source tree structure consolidated
under `HellionChat.*`. Plugin refuses to start alongside an active upstream Chat 2 (bilingual
conflict message). SQLite native pinned to 3.50.3 (CVE-2025-6965, CVE-2025-7709). Tab layout default
for new installs and users on config version 12 or older restructured (5 thematic tabs instead of 6+
kitchen-sink). Sweep of critical and major findings from the codebase audit incorporated.
[Release Notes 1.0.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.0)
@@ -585,9 +641,9 @@ audit incorporated.
## [0.6.1] — 2026-05-03 — Pop-Out Discoverability & /tell Auto-Pop-Out
Pop-out button visible in the chat header, one-time hint banner for the pop-out feature. New setting "Open new /tell
tabs directly as pop-out". Pop-out input is now active by default. Bug fixes: ghost windows on LRU-drop / logout, dead
zone below the input bar when the hint banner is active.
Pop-out button visible in the chat header, one-time hint banner for the pop-out feature. New setting
"Open new /tell tabs directly as pop-out". Pop-out input is now active by default. Bug fixes: ghost
windows on LRU-drop / logout, dead zone below the input bar when the hint banner is active.
[Release Notes 0.6.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.1)
@@ -595,9 +651,10 @@ zone below the input bar when the hint banner is active.
## [0.6.0] — 2026-05-03 — UX Polish: Pop-Out Input + Colour Presets
Two opt-in UX features. Pop-out windows optionally get a compact input bar with a channel-coloured icon button and an
independent text buffer per pop-out. Seven built-in colour presets (Classic, High Contrast, Pastel, Dark Mode Tuned,
Hellion, Night Blue, Indigo Violet) for one-click apply. Configuration migration v10 → v11.
Two opt-in UX features. Pop-out windows optionally get a compact input bar with a channel-coloured
icon button and an independent text buffer per pop-out. Seven built-in colour presets (Classic, High
Contrast, Pastel, Dark Mode Tuned, Hellion, Night Blue, Indigo Violet) for one-click apply.
Configuration migration v10 → v11.
[Release Notes 0.6.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.0)
@@ -605,9 +662,9 @@ Hellion, Night Blue, Indigo Violet) for one-click apply. Configuration migration
## [0.5.4] — 2026-05-02 — WrapText Hardening
`ImGuiUtil.WrapText` rewritten from pointer arithmetic to Span- and index-based control flow. Permanently closes the
recurring CodeQL critical alert "unvalidated local pointer arithmetic". No user-visible behaviour change — word-wrap
output is byte-identical to 0.5.3.
`ImGuiUtil.WrapText` rewritten from pointer arithmetic to Span- and index-based control flow.
Permanently closes the recurring CodeQL critical alert "unvalidated local pointer arithmetic". No
user-visible behaviour change — word-wrap output is byte-identical to 0.5.3.
[Release Notes 0.5.4](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.4)
@@ -615,8 +672,8 @@ output is byte-identical to 0.5.3.
## [0.5.3] — 2026-05-02 — Pointer Arithmetic Hardening
First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Encoded byte buffer length is validated via
`GetByteCount` before pointer arithmetic.
First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Encoded byte buffer
length is validated via `GetByteCount` before pointer arithmetic.
[Release Notes 0.5.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.3)
@@ -624,7 +681,8 @@ First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Enco
## Earlier Versions
Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on the Gitea release stream:
Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on the Gitea release
stream:
[All Releases](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases)
@@ -632,6 +690,7 @@ Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on
## Maintenance Note
The source of truth for the user-facing changelog is the `changelog:` block in `HellionChat/HellionChat.yaml`.
`repo.json` and the GitHub release body are fed from there. This file (`docs/CHANGELOG.md`) is a curated summary with
links to the release pages and is updated manually on each version bump.
The source of truth for the user-facing changelog is the `changelog:` block in
`HellionChat/HellionChat.yaml`. `repo.json` and the GitHub release body are fed from there. This
file (`docs/CHANGELOG.md`) is a curated summary with links to the release pages and is updated
manually on each version bump.
+47 -37
View File
@@ -1,11 +1,12 @@
# Contributors — Hellion Chat
Hellion Chat is a one-person project on the code side. But without the people on this page, the bug fixes and UX
improvements that have landed since the early versions would not exist. Every entry here has made the plugin concretely
better.
Hellion Chat is a one-person project on the code side. But without the people on this page, the bug
fixes and UX improvements that have landed since the early versions would not exist. Every entry
here has made the plugin concretely better.
Attribution for the upstream Chat 2 authors (Infi and Anna) is intentionally in [`../NOTICE.md`](../NOTICE.md), not
here. This file covers contributions to the Hellion Chat side specifically.
Attribution for the upstream Chat 2 authors (Infi and Anna) is intentionally in
[`../NOTICE.md`](../NOTICE.md), not here. This file covers contributions to the Hellion Chat side
specifically.
---
@@ -13,13 +14,14 @@ here. This file covers contributions to the Hellion Chat side specifically.
### JonKazama (Florian Wathling) — Maintainer
Hellion Chat is my first FFXIV plugin and my first larger C#/Dalamud project. My professional background is web
development (Next.js, React, TypeScript, Prisma). Plugin development in an unfamiliar codebase, ImGui, FFXIV game hooks
and the entire Dalamud stack were new territory.
Hellion Chat is my first FFXIV plugin and my first larger C#/Dalamud project. My professional
background is web development (Next.js, React, TypeScript, Prisma). Plugin development in an
unfamiliar codebase, ImGui, FFXIV game hooks and the entire Dalamud stack were new territory.
Privacy-first defaults, per-channel retention, Auto-Tell-Tabs, pop-out input, ChatColours presets, the Hellion theme
plus Exo 2 font, and the v1.0.0 standalone cut are the Hellion-specific surface areas I built on top of the Chat 2
foundation. The learning story behind that is in [`LEARNING-JOURNEY.md`](LEARNING-JOURNEY.md).
Privacy-first defaults, per-channel retention, Auto-Tell-Tabs, pop-out input, ChatColours presets,
the Hellion theme plus Exo 2 font, and the v1.0.0 standalone cut are the Hellion-specific surface
areas I built on top of the Chat 2 foundation. The learning story behind that is in
[`LEARNING-JOURNEY.md`](LEARNING-JOURNEY.md).
Hellion Chat is part of [Hellion Online Media](https://hellion-media.de).
@@ -27,38 +29,45 @@ Hellion Chat is part of [Hellion Online Media](https://hellion-media.de).
## Testers
A quick note: I do not test this plugin alone. The people listed here reported bugs before they hit more users, raised
UX problems I had gone blind to, and brought in feature requests that pushed the plugin in directions I would not have
gone on my own. That is not a given. External testers are worth their time.
A quick note: I do not test this plugin alone. The people listed here reported bugs before they hit
more users, raised UX problems I had gone blind to, and brought in feature requests that pushed the
plugin in directions I would not have gone on my own. That is not a given. External testers are
worth their time.
### Carl Beleandis (Carla) — Beta Tester
Carl has been testing since the bootstrap phase and has shaped both the pop-out mechanics and the theme direction.
Feedback comes direct and without detours, which is exactly what I need when testing.
Carl has been testing since the bootstrap phase and has shaped both the pop-out mechanics and the
theme direction. Feedback comes direct and without detours, which is exactly what I need when
testing.
Concrete contributions:
- **Pop-out discoverability** — pointing out that pop-outs were only reachable via right-click triggered the header
button and the one-time hint banner in v0.6.1. I knew the right-click path by heart and had stopped seeing that new
users could not find the feature at all.
- **/tell pop-out mode** — the request to open /tell tabs directly as a pop-out instead of going through the tab sidebar
landed in v0.6.1 as an opt-in settings toggle. Bonus: during implementation an old ghost-window bug surfaced (LRU drop
left pop-out windows as ghosts), which got fixed at the same time.
- **Theme variants with brightness gradations** — the request for a green family shifted my thinking from "one theme =
one colour" to "theme families with mood variants". On the [roadmap](ROADMAP.md) for a later cycle.
- **Pop-out discoverability** — pointing out that pop-outs were only reachable via right-click
triggered the header button and the one-time hint banner in v0.6.1. I knew the right-click path by
heart and had stopped seeing that new users could not find the feature at all.
- **/tell pop-out mode** — the request to open /tell tabs directly as a pop-out instead of going
through the tab sidebar landed in v0.6.1 as an opt-in settings toggle. Bonus: during
implementation an old ghost-window bug surfaced (LRU drop left pop-out windows as ghosts), which
got fixed at the same time.
- **Theme variants with brightness gradations** — the request for a green family shifted my thinking
from "one theme = one colour" to "theme families with mood variants". On the [roadmap](ROADMAP.md)
for a later cycle.
### Jin (Jingliu) — Alpha Tester
Jin is the active tester from day one and pushed the pop-out workflow architecture in a different direction.
Jin is the active tester from day one and pushed the pop-out workflow architecture in a different
direction.
Concrete contributions:
- **Pop-out tab with input bar** — the suggestion to be able to type in a pop-out (instead of just reading) triggered
the v0.6.0 pop-out input bar. That was a larger refactor: the input layer from `ChatLogWindow` had to be opened up so
it could also live in `Popout.cs`, with an independent text buffer and history cursor per pop-out. It dominated the
cycle because the design had to be clean before any code could happen.
- **TempTell persistence** — the request for /tell tabs to survive a relog via a pin toggle is on the
[roadmap](ROADMAP.md) for a later cycle. It touches the tab system architecturally and needs its own design work.
- **Pop-out tab with input bar** — the suggestion to be able to type in a pop-out (instead of just
reading) triggered the v0.6.0 pop-out input bar. That was a larger refactor: the input layer from
`ChatLogWindow` had to be opened up so it could also live in `Popout.cs`, with an independent text
buffer and history cursor per pop-out. It dominated the cycle because the design had to be clean
before any code could happen.
- **TempTell persistence** — the request for /tell tabs to survive a relog via a pin toggle is on
the [roadmap](ROADMAP.md) for a later cycle. It touches the tab system architecturally and needs
its own design work.
---
@@ -69,15 +78,16 @@ Hellion-specific UI strings are maintained in `HellionChat/Resources/HellionStri
- **German (DE):** JonKazama (native speaker, primary project language)
Upstream language files (`Language.<lang>.resx`) are not covered here. They are maintained via the
[Chat 2 Crowdin project](https://github.com/Infiziert90/ChatTwo); Crowdin translators are listed in the plugin settings
under **Info → "Chat 2 community translators"**.
[Chat 2 Crowdin project](https://github.com/Infiziert90/ChatTwo); Crowdin translators are listed in
the plugin settings under **Info → "Chat 2 community translators"**.
---
## How to Contribute
Bug reports, feature requests and feedback are welcome — the best place to reach me is the Hellion Forge Discord:
[discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR). Join and ping me in the Hellion Chat channel.
Bug reports, feature requests and feedback are welcome — the best place to reach me is the Hellion
Forge Discord: [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR). Join and ping me in the
Hellion Chat channel.
For pull requests and contribution guidelines see [`../CONTRIBUTING.md`](../CONTRIBUTING.md), Code of Conduct in
[`../CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md).
For pull requests and contribution guidelines see [`../CONTRIBUTING.md`](../CONTRIBUTING.md), Code
of Conduct in [`../CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md).
+47 -38
View File
@@ -1,28 +1,31 @@
# Hellion Chat IPC Integration Guide
This document describes the inter-plugin-communication (IPC) channels that Hellion Chat exposes to other Dalamud
plugins. Two integration surfaces are covered: the **Context Menu IPC** for adding custom items to Hellion Chat's
right-click menus, and the **Typing State IPC** for reacting to the user's input-box activity.
This document describes the inter-plugin-communication (IPC) channels that Hellion Chat exposes to
other Dalamud plugins. Two integration surfaces are covered: the **Context Menu IPC** for adding
custom items to Hellion Chat's right-click menus, and the **Typing State IPC** for reacting to the
user's input-box activity.
---
## Compatibility with Chat 2
Hellion Chat is a standalone fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2). The IPC surface is one
of the parts the fork inherits directly: the same call shapes, the same tuple payloads, the same call semantics, the
same lifecycle. We did not redesign the API, we re-published it under our own plugin name.
Hellion Chat is a standalone fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
The IPC surface is one of the parts the fork inherits directly: the same call shapes, the same tuple
payloads, the same call semantics, the same lifecycle. We did not redesign the API, we re-published
it under our own plugin name.
Concretely, this means:
- **Tuple shapes are identical.** A subscriber that worked against Chat 2's `ChatTwo.Invoke` works against Hellion
Chat's `HellionChat.Invoke` without any code change beyond the channel string.
- **Lifecycle is identical.** The `Available` ping fires when the plugin becomes ready, your subscriber re-registers,
and the registration ID is returned by the same `Register` call as before.
- **Channel-name prefix changed in v1.0.0.** Every `ChatTwo.*` channel name is now `HellionChat.*`. Existing third-party
integrations need a one-line rename per channel string and nothing else.
- **Tuple shapes are identical.** A subscriber that worked against Chat 2's `ChatTwo.Invoke` works
against Hellion Chat's `HellionChat.Invoke` without any code change beyond the channel string.
- **Lifecycle is identical.** The `Available` ping fires when the plugin becomes ready, your
subscriber re-registers, and the registration ID is returned by the same `Register` call as
before.
- **Channel-name prefix changed in v1.0.0.** Every `ChatTwo.*` channel name is now `HellionChat.*`.
Existing third-party integrations need a one-line rename per channel string and nothing else.
If your plugin already supports Chat 2 and you want to add Hellion Chat support, the cleanest path is to bind both
prefixes and treat whichever one becomes available first as the active host.
If your plugin already supports Chat 2 and you want to add Hellion Chat support, the cleanest path
is to bind both prefixes and treat whichever one becomes available first as the active host.
---
@@ -41,18 +44,20 @@ prefixes and treat whichever one becomes available first as the active host.
## Context Menu IPC
Use this surface to draw your own selectables inside Hellion Chat's right-click context menus. All registrations are
called inside an ImGui `BeginMenu`, so anything you draw appears as a regular menu entry.
Use this surface to draw your own selectables inside Hellion Chat's right-click context menus. All
registrations are called inside an ImGui `BeginMenu`, so anything you draw appears as a regular menu
entry.
### Lifecycle
1. Subscribe to `HellionChat.Available`. The host fires this once when it loads or reloads, so your plugin can
re-register without polling.
2. Call `HellionChat.Register` to obtain a registration ID. Save it. You need it to filter `Invoke` callbacks that
target your registration and to call `Unregister` later.
3. Subscribe to `HellionChat.Invoke` and draw your menu items inside the handler when the `id` matches your saved
registration ID.
4. On plugin disable or unload, call `HellionChat.Unregister` with your saved ID and unsubscribe from `Invoke`.
1. Subscribe to `HellionChat.Available`. The host fires this once when it loads or reloads, so your
plugin can re-register without polling.
2. Call `HellionChat.Register` to obtain a registration ID. Save it. You need it to filter `Invoke`
callbacks that target your registration and to call `Unregister` later.
3. Subscribe to `HellionChat.Invoke` and draw your menu items inside the handler when the `id`
matches your saved registration ID.
4. On plugin disable or unload, call `HellionChat.Unregister` with your saved ID and unsubscribe
from `Invoke`.
### Example
@@ -137,12 +142,14 @@ If your plugin already integrates with `ChatTwo.*`, the rename is the only requi
## Typing State IPC
Use this surface when you need to know whether the player is currently interacting with Hellion Chat's input box. Useful
for typing indicators, keyboard-shortcut suppression, or HUD elements that hide while the user is typing.
Use this surface when you need to know whether the player is currently interacting with Hellion
Chat's input box. Useful for typing indicators, keyboard-shortcut suppression, or HUD elements that
hide while the user is typing.
### Tuple Payload
Both `HellionChat.GetChatInputState` (poll) and `HellionChat.ChatInputStateChanged` (event) return the same tuple:
Both `HellionChat.GetChatInputState` (poll) and `HellionChat.ChatInputStateChanged` (event) return
the same tuple:
```cs
(bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType)
@@ -159,17 +166,19 @@ Both `HellionChat.GetChatInputState` (poll) and `HellionChat.ChatInputStateChang
### Where `ChannelType` comes from
`ChannelType` is the `HellionChat.Code.ChatType` enum value representing the target channel for the current submission.
It is sourced from the active tab's `UsedChannel` (`HellionChat/Configuration.cs`), which the plugin keeps in sync by
hooking the in-game shell (`HellionChat/GameFunctions/Chat.cs`) and by resolving temporary overrides inside the chat UI
(`HellionChat/Ui/ChatLogWindow.cs:597`). `InputChannel` values are converted into the exported `ChatType` via
`HellionChat/Code/InputChannelExt.ToChatType`.
`ChannelType` is the `HellionChat.Code.ChatType` enum value representing the target channel for the
current submission. It is sourced from the active tab's `UsedChannel`
(`HellionChat/Configuration.cs`), which the plugin keeps in sync by hooking the in-game shell
(`HellionChat/GameFunctions/Chat.cs`) and by resolving temporary overrides inside the chat UI
(`HellionChat/Ui/ChatLogWindow.cs:597`). `InputChannel` values are converted into the exported
`ChatType` via `HellionChat/Code/InputChannelExt.ToChatType`.
### Behavior
- `ChatInputStateChanged` fires once immediately after subscribe so you do not need a separate `GetChatInputState` poll
for the initial snapshot.
- After that it fires only when one or more fields actually change, so it is safe to subscribe without rate-limiting.
- `ChatInputStateChanged` fires once immediately after subscribe so you do not need a separate
`GetChatInputState` poll for the initial snapshot.
- After that it fires only when one or more fields actually change, so it is safe to subscribe
without rate-limiting.
- `GetChatInputState` is available for one-shot polls, e.g. on plugin enable.
### Example 2
@@ -223,7 +232,7 @@ Same shape as the Context Menu surface — only the channel-name prefix needs th
## License & Attribution
This guide and the IPC surface it documents derive directly from the Chat 2 codebase. Hellion Chat is licensed under
[EUPL-1.2](LICENSE), and credit for the original IPC design and implementation goes to
**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and **[Anna](https://github.com/anna-is-cute)**,— see
[`NOTICE.md`](NOTICE.md) for full attribution.
This guide and the IPC surface it documents derive directly from the Chat 2 codebase. Hellion Chat
is licensed under [EUPL-1.2](LICENSE), and credit for the original IPC design and implementation
goes to **[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
**[Anna](https://github.com/anna-is-cute)**,— see [`NOTICE.md`](NOTICE.md) for full attribution.
+189 -159
View File
@@ -2,40 +2,43 @@
## Background
I am self-taught. Hellion Chat is my first FFXIV plugin and my first larger C# project. My professional background is
web development (Next.js, React, TypeScript, Prisma, MySQL) — browser world with a JavaScript toolchain. I knew C# only
superficially before this project, ImGui not at all, and Dalamud only as an end user through other plugins.
I am self-taught. Hellion Chat is my first FFXIV plugin and my first larger C# project. My
professional background is web development (Next.js, React, TypeScript, Prisma, MySQL) — browser
world with a JavaScript toolchain. I knew C# only superficially before this project, ImGui not at
all, and Dalamud only as an end user through other plugins.
When I get stuck somewhere, I use AI tools like Claude Code as a pair assistant. What that looks like exactly and which
classification I use is documented transparently in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md).
When I get stuck somewhere, I use AI tools like Claude Code as a pair assistant. What that looks
like exactly and which classification I use is documented transparently in
[`AI_DISCLOSURE.md`](AI_DISCLOSURE.md).
---
## Why a chat plugin at all?
Hellion Chat is not meant to replace Chat 2. Chat 2 delivers a complete chat experience with full history, filters,
search and replay. For most users that is exactly the right thing.
Hellion Chat is not meant to replace Chat 2. Chat 2 delivers a complete chat experience with full
history, filters, search and replay. For most users that is exactly the right thing.
### Two million messages in two years
My desire for a tighter default was honestly personal at first. After two years with Chat 2 my database had grown to
over two million messages, the majority of them /say, /shout and /yell from complete strangers in Limsa. That is exactly
what makes Chat 2's full history useful, and most users are happy to keep it. My own preference wanted a smaller
default. So I built this fork.
My desire for a tighter default was honestly personal at first. After two years with Chat 2 my
database had grown to over two million messages, the majority of them /say, /shout and /yell from
complete strangers in Limsa. That is exactly what makes Chat 2's full history useful, and most users
are happy to keep it. My own preference wanted a smaller default. So I built this fork.
### Greeter in several clubs
There was a second use case: I am active as a greeter in several FFXIV clubs. The vanilla chat interface is not enough
for greeter work. Parallel /tell conversations write into a single tab at the same time, and I constantly lose track of
who wrote what. Auto-Tell-Tabs (one of the early Hellion Chat features) came directly from this workflow: one tab per
conversation partner, automatically spawned, with a manual greeted status. The privacy hygiene benefit was a nice bonus,
There was a second use case: I am active as a greeter in several FFXIV clubs. The vanilla chat
interface is not enough for greeter work. Parallel /tell conversations write into a single tab at
the same time, and I constantly lose track of who wrote what. Auto-Tell-Tabs (one of the early
Hellion Chat features) came directly from this workflow: one tab per conversation partner,
automatically spawned, with a manual greeted status. The privacy hygiene benefit was a nice bonus,
not the trigger.
### Hellion Online Media
The privacy defaults also reflect a position from my main work. Hellion Online Media is my sole proprietorship, and data
protection toward clients is not a marketing slogan there but operationally relevant. This fork is the plugin form of
the same stance.
The privacy defaults also reflect a position from my main work. Hellion Online Media is my sole
proprietorship, and data protection toward clients is not a marketing slogan there but operationally
relevant. This fork is the plugin form of the same stance.
---
@@ -45,90 +48,99 @@ Three reasons, in descending order of importance.
### Defaults are not negotiable, including mine
Privacy-first as a default is a minority position. Chat 2 rightly serves the broad majority with full history as the
default. Changing those defaults upstream would have been wrong. I would have flipped the standard for a large user base
that wanted it as it was. A clean separation through a dedicated plugin slot was the more respectful path.
Privacy-first as a default is a minority position. Chat 2 rightly serves the broad majority with
full history as the default. Changing those defaults upstream would have been wrong. I would have
flipped the standard for a large user base that wanted it as it was. A clean separation through a
dedicated plugin slot was the more respectful path.
### The web interface had to go
It is a central Chat 2 feature for remote access from a second device. A PR removing it has no chance in a
well-maintained upstream project, and that is correct. But exactly that web interface conflicts with the privacy-first
premise of this fork: a chat plugin that starts a local HTTP server is too large an attack surface for my threat model.
So out it went.
It is a central Chat 2 feature for remote access from a second device. A PR removing it has no
chance in a well-maintained upstream project, and that is correct. But exactly that web interface
conflicts with the privacy-first premise of this fork: a chat plugin that starts a local HTTP server
is too large an attack surface for my threat model. So out it went.
### Velocity
A solo-maintainer project with a small tester pool can iterate faster than an established plugin with a large user base.
That is not a criticism of upstream but a different optimization. I do not need roadmap alignment, reviewer
availability, or to spread audit consequences like the web interface removal across multiple releases.
A solo-maintainer project with a small tester pool can iterate faster than an established plugin
with a large user base. That is not a criticism of upstream but a different optimization. I do not
need roadmap alignment, reviewer availability, or to spread audit consequences like the web
interface removal across multiple releases.
EUPL-1.2 explicitly allows all of this with clear attribution. The code is open under the same license as Chat 2. Infi,
Anna, or anyone else can look in, take ideas, ask questions, or simply ignore the fork. All three are fine with me.
EUPL-1.2 explicitly allows all of this with clear attribution. The code is open under the same
license as Chat 2. Infi, Anna, or anyone else can look in, take ideas, ask questions, or simply
ignore the fork. All three are fine with me.
---
## How I release this fast
Anyone looking at the repo sees a lot of releases and a high commit count in a short time. Both tend to read as red
flags from the outside: AI slop, salami tactics, code spam. In Hellion Chat both are deliberate decisions, and I would
rather explain them once than justify them later.
Anyone looking at the repo sees a lot of releases and a high commit count in a short time. Both tend
to read as red flags from the outside: AI slop, salami tactics, code spam. In Hellion Chat both are
deliberate decisions, and I would rather explain them once than justify them later.
### Groundwork, long before the fork existed
Before I typed the first line into `HellionChat/`, I spent weeks as a reader. Using Chat 2 in-game and playing around
with it. Going through issues in the upstream tracker, especially the closed ones, because that is where you see how
Infi and Anna narrow down bugs. Reading commits, including older ones, to understand _why_ an architecture decision was
made, not just _that_ it was made. If I know today where things live in the codebase, it is not because I navigate
codebases particularly fast but because I read the code beforehand.
Before I typed the first line into `HellionChat/`, I spent weeks as a reader. Using Chat 2 in-game
and playing around with it. Going through issues in the upstream tracker, especially the closed
ones, because that is where you see how Infi and Anna narrow down bugs. Reading commits, including
older ones, to understand _why_ an architecture decision was made, not just _that_ it was made. If I
know today where things live in the codebase, it is not because I navigate codebases particularly
fast but because I read the code beforehand.
That sounds obvious. It is not. The usual order for solo forks is fork first, understand later. I did it the other way
around.
That sounds obvious. It is not. The usual order for solo forks is fork first, understand later. I
did it the other way around.
One thing I noticed reading the codebase closely: some patterns felt familiar in ways I had not expected, structural
choices and comment styles that show up across a lot of modern plugin and tooling code regardless of how it was written.
Nothing worth reading into. Coding workflows have changed a lot in the last few years across the board, and the traces
of that show up everywhere. It did make me less self-conscious about my own workflow.
One thing I noticed reading the codebase closely: some patterns felt familiar in ways I had not
expected, structural choices and comment styles that show up across a lot of modern plugin and
tooling code regardless of how it was written. Nothing worth reading into. Coding workflows have
changed a lot in the last few years across the board, and the traces of that show up everywhere. It
did make me less self-conscious about my own workflow.
### Infi and Anna's codebase
Hellion Chat builds on a foundation that is already flat. Chat 2 is cleanly structured, naming conventions are
consistent, and the separation between layers (storage, UI, game hooks, IPC) is clearly drawn. That is not a given in
open-source plugin land, and it is the main reason Hellion-specific features often slot in "almost natively". I do not
have to untangle spaghetti before I can put something of my own next to it.
Hellion Chat builds on a foundation that is already flat. Chat 2 is cleanly structured, naming
conventions are consistent, and the separation between layers (storage, UI, game hooks, IPC) is
clearly drawn. That is not a given in open-source plugin land, and it is the main reason
Hellion-specific features often slot in "almost natively". I do not have to untangle spaghetti
before I can put something of my own next to it.
Side note: even during the first codebase walkthrough with Claude, the comment came up several times that the
architecture is unusually tidy and has several extension points prepared. That carries weight because it comes from
outside, but the actual credit goes to Infi and Anna, not Claude.
Side note: even during the first codebase walkthrough with Claude, the comment came up several times
that the architecture is unusually tidy and has several extension points prepared. That carries
weight because it comes from outside, but the actual credit goes to Infi and Anna, not Claude.
### Atomic work, small commits
One commit, one logical change. If I fix a bug, rename a variable and add a comment at the same time, that is three
commits, not one. Sounds like micro-management, it is not. If a bug surfaces in six months and I need `git bisect`, I
find the broken change in two minutes instead of two hours. With a 4000-line mega-commit I get to guess which of the
hundred changes is the broken one.
One commit, one logical change. If I fix a bug, rename a variable and add a comment at the same
time, that is three commits, not one. Sounds like micro-management, it is not. If a bug surfaces in
six months and I need `git bisect`, I find the broken change in two minutes instead of two hours.
With a 4000-line mega-commit I get to guess which of the hundred changes is the broken one.
I kept this style deliberately also because Infi works the same way upstream. Sometimes a six-line commit, sometimes
just a typo fix. That is not a weakness, it is a decision for readable Git history. Keeping the style in the fork is a
respect move: anyone comparing both repos should have the same reading rhythm.
I kept this style deliberately also because Infi works the same way upstream. Sometimes a six-line
commit, sometimes just a typo fix. That is not a weakness, it is a decision for readable Git
history. Keeping the style in the fork is a respect move: anyone comparing both repos should have
the same reading rhythm.
Personal bonus: small commits force me to think through and name each step individually. If I cannot explain what a
commit does in two sentences, the change is probably not clear enough yet. At beginner level that is a built-in sanity
check I would not have with a big-bang commit.
Personal bonus: small commits force me to think through and name each step individually. If I cannot
explain what a commit does in two sentences, the change is probably not clear enough yet. At
beginner level that is a built-in sanity check I would not have with a big-bang commit.
### AI as an accelerator, honestly
Yes, AI helps with velocity, and not a little. Without CodeRabbit I would not have found critical bugs like
`Equals/GetHashCode` anti-patterns, hook subscription leaks and TOCTOU races. I am simply too inexperienced for that
class of findings, and I write that exactly as it is.
Yes, AI helps with velocity, and not a little. Without CodeRabbit I would not have found critical
bugs like `Equals/GetHashCode` anti-patterns, hook subscription leaks and TOCTOU races. I am simply
too inexperienced for that class of findings, and I write that exactly as it is.
What I do not do: blindly take code because a tool marked it as a fix. On several CodeRabbit findings, the original
commits from Infi or Anna even included a Stack Overflow link explaining why a particular spot looks the way it does. I
read those before touching anything. Understand first, then change, then commit. That is the difference between "AI
gives me code, I push" and "AI shows me where it breaks, I decide".
What I do not do: blindly take code because a tool marked it as a fix. On several CodeRabbit
findings, the original commits from Infi or Anna even included a Stack Overflow link explaining why
a particular spot looks the way it does. I read those before touching anything. Understand first,
then change, then commit. That is the difference between "AI gives me code, I push" and "AI shows me
where it breaks, I decide".
Classification and concrete examples of AI usage are in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). This section was only
about the velocity aspect: research plus a clean codebase plus atomic commits plus AI-assisted review sparring are the
four factors together. No single one explains the pace on its own.
Classification and concrete examples of AI usage are in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). This
section was only about the velocity aspect: research plus a clean codebase plus atomic commits plus
AI-assisted review sparring are the four factors together. No single one explains the pace on its
own.
---
@@ -136,48 +148,53 @@ four factors together. No single one explains the pace on its own.
### Type system? Less of a shock than expected
C# after TypeScript was more comfortable than expected. Properties instead of getters/setters are clean, nullable
reference types feel like `strict: true` in TypeScript. What was unfamiliar was having to think explicitly about value
types versus reference types (`struct` vs. `class` with real behavioural consequences), and generics with constraints
are syntactically different enough that I stumble on them while reading. `async`/`await` is semantically similar, but
threading models are more explicit in C#: `Task.Run`, `ConfigureAwait`, synchronization contexts. That cost me several
bugs before I understood when the main thread (in plugin land: the framework tick) is actually critical.
C# after TypeScript was more comfortable than expected. Properties instead of getters/setters are
clean, nullable reference types feel like `strict: true` in TypeScript. What was unfamiliar was
having to think explicitly about value types versus reference types (`struct` vs. `class` with real
behavioural consequences), and generics with constraints are syntactically different enough that I
stumble on them while reading. `async`/`await` is semantically similar, but threading models are
more explicit in C#: `Task.Run`, `ConfigureAwait`, synchronization contexts. That cost me several
bugs before I understood when the main thread (in plugin land: the framework tick) is actually
critical.
### Build toolchain: similar, but different
`dotnet` CLI, csproj XML, NuGet are functionally not far from npm and tsconfig. But the XML format of csproj is a
different language than JSON configs. The lock file (`packages.lock.json`) had to be actively enabled
(`RestorePackagesWithLockFile=true`); that is not the default. In the web stack, lock-file-first is standard, in the
.NET stack apparently not. That was a real surprise.
`dotnet` CLI, csproj XML, NuGet are functionally not far from npm and tsconfig. But the XML format
of csproj is a different language than JSON configs. The lock file (`packages.lock.json`) had to be
actively enabled (`RestorePackagesWithLockFile=true`); that is not the default. In the web stack,
lock-file-first is standard, in the .NET stack apparently not. That was a real surprise.
### ImGui is a different world
Immediate-mode rendering has nothing in common with React component trees. There is no virtual DOM, no reconciliation,
no "component state". Every frame the code redraws the UI from scratch, and state lives either in local variables I
manage myself or in ImGui's own ID stack logic.
Immediate-mode rendering has nothing in common with React component trees. There is no virtual DOM,
no reconciliation, no "component state". Every frame the code redraws the UI from scratch, and state
lives either in local variables I manage myself or in ImGui's own ID stack logic.
What is two lines of `useState` in React is a member field plus manual ID stamps on widgets in ImGui, otherwise two
selectables in the same loop collide because they fall back to the same ID. The ID stack collision in `SearchSelector`
(fixed in v1.0.0) was exactly that symptom: all selectables fell back to the same ambiguous ID until I mixed the row
index into the PushID. Classic "why is the wrong entry getting clicked" bug that you only find once you understand how
ImGui handles IDs internally.
What is two lines of `useState` in React is a member field plus manual ID stamps on widgets in
ImGui, otherwise two selectables in the same loop collide because they fall back to the same ID. The
ID stack collision in `SearchSelector` (fixed in v1.0.0) was exactly that symptom: all selectables
fell back to the same ambiguous ID until I mixed the row index into the PushID. Classic "why is the
wrong entry getting clicked" bug that you only find once you understand how ImGui handles IDs
internally.
### Dalamud specifics
Plugin lifecycle, IPC subscriber pattern, hook system for game functions, game object threading. Much of that was only
understandable through reading the upstream codebase and through [dalamud.dev](https://dalamud.dev). Search results for
"Dalamud" often turn up outdated API examples from old versions. dalamud.dev is the reliable source. If someone is just
starting out: go there, not to Stack Overflow.
Plugin lifecycle, IPC subscriber pattern, hook system for game functions, game object threading.
Much of that was only understandable through reading the upstream codebase and through
[dalamud.dev](https://dalamud.dev). Search results for "Dalamud" often turn up outdated API examples
from old versions. dalamud.dev is the reliable source. If someone is just starting out: go there,
not to Stack Overflow.
### The day DalamudPackager cost me a day
Dalamud SDK 15 ships its own default packager that writes icons and image URLs into the manifest. I had carried over a
`DalamudPackager.targets` file from the upstream repo with a `HandleImages` override, and it was overriding the SDK
default. Result: the manifest had no `IconUrl` anymore, and the plugin appeared in the plugin list without an icon.
Dalamud SDK 15 ships its own default packager that writes icons and image URLs into the manifest. I
had carried over a `DalamudPackager.targets` file from the upstream repo with a `HandleImages`
override, and it was overriding the SDK default. Result: the manifest had no `IconUrl` anymore, and
the plugin appeared in the plugin list without an icon.
The symptom was easy to spot, the cause cost a day. I had treated the override file as mandatory when it was not.
Removed in v0.5.2, SDK default running since then. Lesson: start with defaults, add overrides only when the default
demonstrably does not fit.
The symptom was easy to spot, the cause cost a day. I had treated the override file as mandatory
when it was not. Removed in v0.5.2, SDK default running since then. Lesson: start with defaults, add
overrides only when the default demonstrably does not fit.
---
@@ -185,73 +202,82 @@ demonstrably does not fit.
### Refactoring in an unfamiliar codebase
The standalone cut in v1.0.0 migrated the entire `ChatTwo.*` identity to `HellionChat.*`. That sounds like find and
replace. It was not.
The standalone cut in v1.0.0 migrated the entire `ChatTwo.*` identity to `HellionChat.*`. That
sounds like find and replace. It was not.
In concrete terms: code namespace across all 80 source files plus 100 using directives plus two FQN aliases plus the
resource designer strings. Six IPC channels renamed (breaking change for third-party plugins, no known integrations).
Repo folder structure (`ChatTwo/` -> `HellionChat/`) including csproj, sln, all GitHub workflows and dependabot.yml.
Public-facing branding in README, repo.json and yaml reformulated to standalone framing.
In concrete terms: code namespace across all 80 source files plus 100 using directives plus two FQN
aliases plus the resource designer strings. Six IPC channels renamed (breaking change for
third-party plugins, no known integrations). Repo folder structure (`ChatTwo/` -> `HellionChat/`)
including csproj, sln, all GitHub workflows and dependabot.yml. Public-facing branding in README,
repo.json and yaml reformulated to standalone framing.
It was not a solo find-and-replace because Unicode string paths in workflow YAMLs need different quoting than C#
strings. Because resource designer files have generated content that not every toolchain tracks. And because the
`ChatTwo.*` IPC channel names are strings in `GetIpcSubscriber` calls: no symbol, no compile error if you miss one. That
is when you find out what stays quiet.
It was not a solo find-and-replace because Unicode string paths in workflow YAMLs need different
quoting than C# strings. Because resource designer files have generated content that not every
toolchain tracks. And because the `ChatTwo.*` IPC channel names are strings in `GetIpcSubscriber`
calls: no symbol, no compile error if you miss one. That is when you find out what stays quiet.
### Security is no longer abstract
Before this project, supply chain security was academic for me. Three concrete lessons changed that.
**SQLite native binary.** I had to pin to 3.50.3 (`SQLitePCLRaw.lib.e_sqlite3` override) because `Microsoft.Data.Sqlite`
was pulling in a transitively referenced library at a version containing CVE-2025-6965 (memory corruption via aggregate
term overflow) and CVE-2025-7709. The managed wrapper was new; the native library was not. Lesson: transitive
dependencies do not audit themselves, you have to look.
**SQLite native binary.** I had to pin to 3.50.3 (`SQLitePCLRaw.lib.e_sqlite3` override) because
`Microsoft.Data.Sqlite` was pulling in a transitively referenced library at a version containing
CVE-2025-6965 (memory corruption via aggregate term overflow) and CVE-2025-7709. The managed wrapper
was new; the native library was not. Lesson: transitive dependencies do not audit themselves, you
have to look.
**Lock file drift.** `packages.lock.json` honoured via `RestorePackagesWithLockFile=true` in the csproj prevents
transitive versions from silently drifting between my machine and CI. I only understood why this is not the default
after a build output mismatch between local and GitHub Actions.
**Lock file drift.** `packages.lock.json` honoured via `RestorePackagesWithLockFile=true` in the
csproj prevents transitive versions from silently drifting between my machine and CI. I only
understood why this is not the default after a build output mismatch between local and GitHub
Actions.
**WrapText and the CodeQL alert that cost three releases.** CodeQL flagged a critical alert in `ImGuiUtil.WrapText` for
unvalidated local pointer arithmetic. v0.5.2 validated an edge case. Alert came back. v0.5.3 checked buffer length via
`GetByteCount` before the pointer math. Alert came back. v0.5.4 rebuilt the whole algorithm on `Span` and int offsets
with a 16 KiB cap on the ArrayPool rent. Only then did it go quiet.
**WrapText and the CodeQL alert that cost three releases.** CodeQL flagged a critical alert in
`ImGuiUtil.WrapText` for unvalidated local pointer arithmetic. v0.5.2 validated an edge case. Alert
came back. v0.5.3 checked buffer length via `GetByteCount` before the pointer math. Alert came back.
v0.5.4 rebuilt the whole algorithm on `Span` and int offsets with a 16 KiB cap on the ArrayPool
rent. Only then did it go quiet.
Lesson: when a static analyser complains three times in a row, the analyser is not oversensitive. The data flow logic
is.
Lesson: when a static analyser complains three times in a row, the analyser is not oversensitive.
The data flow logic is.
### CodeRabbit as an external code reviewer
The v1.0.0 sweep surfaced 3 critical and 21 major findings. Three classes were particularly instructive:
The v1.0.0 sweep surfaced 3 critical and 21 major findings. Three classes were particularly
instructive:
- **`Equals` methods comparing `GetHashCode()`.** Classic hash collision anti-pattern. Sounds like "if hashes are equal
the objects are equal", which is exactly backwards. Hashes can collide; the objects are not equal.
- **`Dispose` methods that only unsubscribe part of their subscriptions.** Leak on every plugin reload. In normal use
you do not notice it immediately; in a long-running test you do.
- **TOCTOU races.** Between a bounds check and a read another thread can swap out the array underneath you
(`GlobalParametersCache`, `AutoTranslate`).
- **`Equals` methods comparing `GetHashCode()`.** Classic hash collision anti-pattern. Sounds like
"if hashes are equal the objects are equal", which is exactly backwards. Hashes can collide; the
objects are not equal.
- **`Dispose` methods that only unsubscribe part of their subscriptions.** Leak on every plugin
reload. In normal use you do not notice it immediately; in a long-running test you do.
- **TOCTOU races.** Between a bounds check and a read another thread can swap out the array
underneath you (`GlobalParametersCache`, `AutoTranslate`).
I had at best read the theory on all of these before, never diagnosed them in my own code. CodeRabbit was the moment
where "academic knowledge" became "okay, that is my code, that is my bug".
I had at best read the theory on all of these before, never diagnosed them in my own code.
CodeRabbit was the moment where "academic knowledge" became "okay, that is my code, that is my bug".
### External testers are worth their weight
Carla's feedback on pop-out discoverability triggered the header button in v0.6.1. That pop-outs were only reachable via
right-click was something I as maintainer had stopped seeing; I knew the path by heart. Carl's request for theme
variants with brightness gradations shifted my thinking from "one theme = one colour" to "theme families with mood
variants". Jingliu asked for TempTell persistence, which puts the tab system architecturally into question.
Carla's feedback on pop-out discoverability triggered the header button in v0.6.1. That pop-outs
were only reachable via right-click was something I as maintainer had stopped seeing; I knew the
path by heart. Carl's request for theme variants with brightness gradations shifted my thinking from
"one theme = one colour" to "theme families with mood variants". Jingliu asked for TempTell
persistence, which puts the tab system architecturally into question.
Solo I would not have seen any of those three things. Full stop.
### release.yml and the YAML rabbit hole
The `release.yml` workflow simply did not fire on the first v0.6.0 tag push. I dug through permissions, secret scopes
and tag trigger configuration for hours before I understood what was actually happening: the PowerShell heredoc footer
in the "Generate release body" step contained a `---` Markdown horizontal rule at column 1, and that terminated the YAML
block scalar of `run: |`. GitHub could not parse the workflow file, so the push-tag trigger never registered.
The `release.yml` workflow simply did not fire on the first v0.6.0 tag push. I dug through
permissions, secret scopes and tag trigger configuration for hours before I understood what was
actually happening: the PowerShell heredoc footer in the "Generate release body" step contained a
`---` Markdown horizontal rule at column 1, and that terminated the YAML block scalar of `run: |`.
GitHub could not parse the workflow file, so the push-tag trigger never registered.
Fix: extracted the footer into an external `.github/release-footer.md`, workflow reads it via `Get-Content`. Lesson: if
a workflow does not trigger, verify first that GitHub can even parse the file. That was one of the bugs where I laughed
briefly after the fix and then asked myself how many other YAML files I had that might have the same trap in them.
Fix: extracted the footer into an external `.github/release-footer.md`, workflow reads it via
`Get-Content`. Lesson: if a workflow does not trigger, verify first that GitHub can even parse the
file. That was one of the bugs where I laughed briefly after the fix and then asked myself how many
other YAML files I had that might have the same trap in them.
---
@@ -259,29 +285,31 @@ briefly after the fix and then asked myself how many other YAML files I had that
### Performance profiling in a game context
The FPS drop bug from upstream Chat 2 ([#145](https://github.com/Infiziert90/ChatTwo/issues/145)) has not been
reproduced or verified in Hellion Chat. v1.0.0 applied several fixes on the suspected paths (DbViewer O(N²) -> O(N),
AutoTranslate lock serialisation, EmoteCache HttpClient reuse), but systematic measurement under load is missing. I
still need to learn how to properly measure what is actually consuming the frame budget in a plugin context.
The FPS drop bug from upstream Chat 2 ([#145](https://github.com/Infiziert90/ChatTwo/issues/145))
has not been reproduced or verified in Hellion Chat. v1.0.0 applied several fixes on the suspected
paths (DbViewer O(N²) -> O(N), AutoTranslate lock serialisation, EmoteCache HttpClient reuse), but
systematic measurement under load is missing. I still need to learn how to properly measure what is
actually consuming the frame budget in a plugin context.
### Native interop and pointer math
Even after the WrapText Span refactor in v0.5.4, pointer math makes me uneasy. ImGui forces you into `unsafe` code in
several places, and the safety margin from the "unbounded ArrayPool allocation" class of bugs is narrower than I would
like. I want to get better at that before touching deeper ImGui custom drawing.
Even after the WrapText Span refactor in v0.5.4, pointer math makes me uneasy. ImGui forces you into
`unsafe` code in several places, and the safety margin from the "unbounded ArrayPool allocation"
class of bugs is narrower than I would like. I want to get better at that before touching deeper
ImGui custom drawing.
### Test discipline for plugin code
The repo currently has no test project. That is a deliberate decision, not a forgotten one. Testing plugin code with
FFXIV hooks and Dalamud lifecycle cleanly is non-trivial, and I had not found an approach that made sense without a
large mocking scaffold. Privacy filter and configuration migration would be good test candidates because they are
isolated. On the list, but not a quick win.
The repo currently has no test project. That is a deliberate decision, not a forgotten one. Testing
plugin code with FFXIV hooks and Dalamud lifecycle cleanly is non-trivial, and I had not found an
approach that made sense without a large mocking scaffold. Privacy filter and configuration
migration would be good test candidates because they are isolated. On the list, but not a quick win.
### Linux quirks under Wine
XDG compliance, libnotify integration, WireGuard network detection, all on the [roadmap](ROADMAP.md), and all
technically still unclear. Wine and sandboxed plugin code do not share all system APIs, and I do not know where the
pitfalls are until I have found them.
XDG compliance, libnotify integration, WireGuard network detection, all on the
[roadmap](ROADMAP.md), and all technically still unclear. Wine and sandboxed plugin code do not
share all system APIs, and I do not know where the pitfalls are until I have found them.
---
@@ -303,10 +331,12 @@ I use Claude Code as an assistant, not as a replacement for my own work.
- Tester communication and roadmap prioritisation
- Reviewing, verifying, pushing
Classification and concrete examples are in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). It matters to me that users and
potential contributors understand how the code came together, especially for a plugin that handles user data.
Classification and concrete examples are in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). It matters to me
that users and potential contributors understand how the code came together, especially for a plugin
that handles user data.
Yes, AI. Yes, alone. Both mentioned more than strictly necessary. Welcome to the open-source plugin climate.
Yes, AI. Yes, alone. Both mentioned more than strictly necessary. Welcome to the open-source plugin
climate.
---
+238 -202
View File
@@ -1,251 +1,281 @@
# Hellion Chat — Roadmap
Planned work after the v1.0.0 standalone cut. This list is intentionally high-level: concrete specs, size estimates and
repro steps live in the internal backlog. External tracking runs via
[Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) with the `roadmap` label once an
item is scheduled for a cycle.
Planned work after the v1.0.0 standalone cut. This list is intentionally high-level: concrete specs,
size estimates and repro steps live in the internal backlog. External tracking runs via
[Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) with the
`roadmap` label once an item is scheduled for a cycle.
Order reflects priority, not a guarantee. Items may shift or be dropped entirely if they turn out to be a poor fit for
the plugin's privacy-first scope during brainstorming.
Order reflects priority, not a guarantee. Items may shift or be dropped entirely if they turn out to
be a poor fit for the plugin's privacy-first scope during brainstorming.
---
## Next Cycle (v1.5.1)
**Honorific Full Gradient Port plus FontAtlas-Defer for a 10× HITCH cut.** v1.5.0 closed the DI-container cycle with
no performance penalty against Chat 2 (77 ms vs 74 ms median first-frame HITCH), but the cross-plugin baseline against
Lightless Sync and XIVInstantMessenger surfaced a clean optimisation: both plugins defer their font-atlas build until
after `Finished loading` and sit at 6-7 ms HITCH, an order of magnitude below the ~75 ms floor that Chat 2 and HellionChat
share. v1.5.1 ports that pattern. Plus the Honorific gradient render path — DTO is gradient-ready since v1.4.7, only the
Wave / Pulse animation port remains. After that, First-Run-Wizard rework with curated defaults beyond the three privacy
profiles, then FR localisation (Hezcal native-speaker review confirmed), then the Plugin Integrations Wave 2-6
(Context-Menu, NotificationMaster, Moodles, ExtraChat, XIVIM Quick-DM). Wine/Linux scroll-rubber-band spike sits as a
low-priority Linux-only investigation at the tail.
**Honorific Full Gradient Port plus FontAtlas-Defer for a 10× HITCH cut.** v1.5.0 closed the
DI-container cycle with no performance penalty against Chat 2 (77 ms vs 74 ms median first-frame
HITCH), but the cross-plugin baseline against Lightless Sync and XIVInstantMessenger surfaced a
clean optimisation: both plugins defer their font-atlas build until after `Finished loading` and sit
at 6-7 ms HITCH, an order of magnitude below the ~75 ms floor that Chat 2 and HellionChat share.
v1.5.1 ports that pattern. Plus the Honorific gradient render path — DTO is gradient-ready since
v1.4.7, only the Wave / Pulse animation port remains. After that, First-Run-Wizard rework with
curated defaults beyond the three privacy profiles, then FR localisation (Hezcal native-speaker
review confirmed), then the Plugin Integrations Wave 2-6 (Context-Menu, NotificationMaster, Moodles,
ExtraChat, XIVIM Quick-DM). Wine/Linux scroll-rubber-band spike sits as a low-priority Linux-only
investigation at the tail.
---
## v1.5.0 — DI Foundation and Service Refactor (released 2026-05-17)
Major architecture cycle. Plugin bootstrap moves to a generic-host DI container
(`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync's `PluginHostFactory`. Service
logging migrates from the static `Plugin.LogProxy` locator (the F12.2 shim from v1.4.7) to typed
`Microsoft.Extensions.Logging.ILogger<T>` via constructor injection, bridged over Dalamud's `IPluginLog` by a custom
`DalamudLogger` trio. 18 instance-class services move to ctor-injected loggers across four slices: data layer,
IPC/integrations, UI window layer, and root. `Plugin.LogProxy` stays for the eight buckets ctor injection cannot
reach — static helpers (`EmoteCache`, `AutoTranslate`, `MemoryUtil`, `WrapperUtil`), Dalamud-reflected types
(`Configuration`), the `Message` data class, and instance classes that only log from static methods (`FontManager`,
one `GameFunctions` site). Plugin.cs finishes at 1012 lines, virtually identical to the pre-cycle 1013 (-1 netto): the
new Phase-1 host build and `Plugin.X` bridge wiring trade out exactly the service and window allocations that previously
lived in `LoadAsync`. Cross-plugin baseline (10 reload-stress runs, 51 active plugins): HellionChat first-frame HITCH
77 ms median, Chat 2 v1.40.2 74 ms median — no DI penalty. The deferred-font-atlas pattern from Lightless and
XIVInstantMessenger is the v1.5.1 follow-up. User-visible: slash-command insert fix cherry-picked from ChatTwo upstream
`ee7768ac` — pasting a slash command into the chat input now replaces existing input instead of concatenating.
Migration v17 stays.
(`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync's
`PluginHostFactory`. Service logging migrates from the static `Plugin.LogProxy` locator (the F12.2
shim from v1.4.7) to typed `Microsoft.Extensions.Logging.ILogger<T>` via constructor injection,
bridged over Dalamud's `IPluginLog` by a custom `DalamudLogger` trio. 18 instance-class services
move to ctor-injected loggers across four slices: data layer, IPC/integrations, UI window layer, and
root. `Plugin.LogProxy` stays for the eight buckets ctor injection cannot reach — static helpers
(`EmoteCache`, `AutoTranslate`, `MemoryUtil`, `WrapperUtil`), Dalamud-reflected types
(`Configuration`), the `Message` data class, and instance classes that only log from static methods
(`FontManager`, one `GameFunctions` site). Plugin.cs finishes at 1012 lines, virtually identical to
the pre-cycle 1013 (-1 netto): the new Phase-1 host build and `Plugin.X` bridge wiring trade out
exactly the service and window allocations that previously lived in `LoadAsync`. Cross-plugin
baseline (10 reload-stress runs, 51 active plugins): HellionChat first-frame HITCH 77 ms median,
Chat 2 v1.40.2 74 ms median — no DI penalty. The deferred-font-atlas pattern from Lightless and
XIVInstantMessenger is the v1.5.1 follow-up. User-visible: slash-command insert fix cherry-picked
from ChatTwo upstream `ee7768ac` — pasting a slash command into the chat input now replaces existing
input instead of concatenating. Migration v17 stays.
---
## 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.
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.
---
## v1.4.9 — Plugin-Load Render Polish (released 2026-05-15)
Tenth sub-patch of the v1.4.x Polish Sweep series. First-frame HITCH drops from ~127 ms median to ~76 ms median (4-reload
sample), comfortably under Dalamud's 100 ms warning threshold. Mechanism: a single `_firstFrameDone` flag inside
`ChatLogWindow` defers six non-essential rendering sections (bottom status bar, channel-name SeString chunks, window
bounds check, v0.6.1 hint banner, autocomplete, input-preview calculation) from frame 0 to frame 1. User sees those
sections ~17 ms (60 fps) later, invisible inside the ~2.5 s font-atlas build window after every reload. Slash-command
registration moved from individual window constructors to a central `SetupCommands` / `TearDownCommands` pair in
`Plugin.cs``/hellion`, `/hellionView`, `/hellionSeString` and `/hellionDebugger` work before their target windows are
opened the first time, and Dalamud's plugin-manager `OpenConfigUi` / `OpenMainUi` buttons hang on the same path.
Plugin-load profiling logs (auto-translate warmup, `MessageStore.Connect`, `MessageStore.Migrate`, `FilterAllTabs`) stay
on at Information level as a regression tripwire. The release also ships a ChatTwo IPC compatibility layer: HellionChat
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 only subscribe to ChatTwo's IPC (Artisan, AllaganTools) keep working without a code change on their
side. Conflict detection prevents ChatTwo from loading in parallel, so there is no slot-collision risk at runtime.
Migration v17 stays (no schema bump). Hypothesis-triage falsified
three of four candidate root causes (font-atlas sync fallback, theme-apply ABGR-cache init, multiple-window render via
lazy-init) — actual cost distributes evenly across ~10 ImGui sections inside ChatLogWindow, so structural rewrite is
deferred to v1.5.x DI-container cycle.
Tenth sub-patch of the v1.4.x Polish Sweep series. First-frame HITCH drops from ~127 ms median to
~76 ms median (4-reload sample), comfortably under Dalamud's 100 ms warning threshold. Mechanism: a
single `_firstFrameDone` flag inside `ChatLogWindow` defers six non-essential rendering sections
(bottom status bar, channel-name SeString chunks, window bounds check, v0.6.1 hint banner,
autocomplete, input-preview calculation) from frame 0 to frame 1. User sees those sections ~17 ms
(60 fps) later, invisible inside the ~2.5 s font-atlas build window after every reload.
Slash-command registration moved from individual window constructors to a central `SetupCommands` /
`TearDownCommands` pair in `Plugin.cs``/hellion`, `/hellionView`, `/hellionSeString` and
`/hellionDebugger` work before their target windows are opened the first time, and Dalamud's
plugin-manager `OpenConfigUi` / `OpenMainUi` buttons hang on the same path. Plugin-load profiling
logs (auto-translate warmup, `MessageStore.Connect`, `MessageStore.Migrate`, `FilterAllTabs`) stay
on at Information level as a regression tripwire. The release also ships a ChatTwo IPC compatibility
layer: HellionChat 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 only subscribe to
ChatTwo's IPC (Artisan, AllaganTools) keep working without a code change on their side. Conflict
detection prevents ChatTwo from loading in parallel, so there is no slot-collision risk at runtime.
Migration v17 stays (no schema bump). Hypothesis-triage falsified three of four candidate root
causes (font-atlas sync fallback, theme-apply ABGR-cache init, multiple-window render via lazy-init)
— actual cost distributes evenly across ~10 ImGui sections inside ChatLogWindow, so structural
rewrite is deferred to v1.5.x DI-container cycle.
## v1.4.8 — Hook-Layer and Polish Quick-Wins (released 2026-05-14)
Ninth sub-patch of the v1.4.x Polish Sweep series. Database Viewer gains an optional FTS5 full-text search across the
full chat history, built asynchronously on first run after the update with a progress toast; the local page-filter
remains the default mode. Custom theme files auto-reload when edited while the theme is active (1 Hz disk-stat throttle,
so per-frame cost is free). Retention sweep no longer blocks the framework thread — `Framework.Run(...).Wait()` is
replaced by `Framework.RunOnTick(...)`, removing the ~194 ms hitch per sweep. Status-bar height is now derived from
`GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders correctly at Windows display scaling above
100 %. Receive-suppressed-tells routing was investigated and **postponed to v1.5.x**: when other plugins suppress tells
via `CheckMessageHandled`, FFXIV's chat-pipeline skips the `RaptureLogModule.AddMsgSourceEntry` path, which means the
`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 anyway. Migration v17 stays (no schema bump). H3 leaves a foundation
note in the Vault (`Projekte/FFXIV/Hellion Chat/v1.5.x Ad-Block Foundation.md`) covering the NoSoliciting filter +
Ninth sub-patch of the v1.4.x Polish Sweep series. Database Viewer gains an optional FTS5 full-text
search across the full chat history, built asynchronously on first run after the update with a
progress toast; the local page-filter remains the default mode. Custom theme files auto-reload when
edited while the theme is active (1 Hz disk-stat throttle, so per-frame cost is free). Retention
sweep no longer blocks the framework thread — `Framework.Run(...).Wait()` is replaced by
`Framework.RunOnTick(...)`, removing the ~194 ms hitch per sweep. Status-bar height is now derived
from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders correctly at
Windows display scaling above 100 %. Receive-suppressed-tells routing was investigated and
**postponed to v1.5.x**: when other plugins suppress tells via `CheckMessageHandled`, FFXIV's
chat-pipeline skips the `RaptureLogModule.AddMsgSourceEntry` path, which means the
`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 anyway. Migration v17 stays
(no schema bump). H3 leaves a foundation note in the Vault
(`Projekte/FFXIV/Hellion Chat/v1.5.x Ad-Block Foundation.md`) covering the NoSoliciting filter +
bubble-layer hook pattern as a ready-made template for the v1.5.x cycle.
---
## v1.4.7 — Backlog Cleanup and Mid-Features (released 2026-05-13)
Eighth sub-patch of the v1.4.x Polish Sweep series. First user-visible feature bundle since v1.4.5. TempTell tabs can
now be pinned via right-click; pinned tabs survive plugin reload and character logout, keep their conversation history
(loaded on demand from the message store on rehydrate), and stay bound to the same `/tell` partner. A hard cap of 5
pinned tabs lives in a pool separate from the 15-tab auto-tell pool, total ceiling 20. The sidebar groups pinned tabs
into their own section with a divider header, and the sidebar width itself is now configurable in **Theme & Layout**
between 44 and 160 px. Honorific glow outlines render when the title carries a Glow colour, opt-in via **Settings →
Integrations → Render glow outlines (Honorific)** (default off). Honorific's gradient (Color3 / GradientColourSet / Wave
/ Pulse) is parsed but rendered statically — a later cycle will port the full animation algorithm or land an upstream
IPC PR for the resolved frame colour. `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` — together they fix a Settings-Save regression where the chat input could pop back to
`/tell <pinned-partner>` after touching settings on a Party or Linkshell tab. Internal items: `IPluginLogProxy`
indirection over Dalamud's `IPluginLog` routes all ~91 `Plugin.Log` call sites through a testable proxy, closing the
F12.1 test-isolation gap (`MessageStore.Migrate0` runs in xUnit now). TempTab counter switched from `Interlocked` cached
field to derived `Tabs.Count(predicate)`. Migration v16 → v17 is additive (new `Tab.IsPinned` flag). Build-Suite floor
688 → 710 (+22 tests across Pin-lifecycle predicates, pool limits, Tab.Clone roundtrip, MessageStore Migrate0
construction, and Honorific TitleData JSON roundtrip).
Eighth sub-patch of the v1.4.x Polish Sweep series. First user-visible feature bundle since v1.4.5.
TempTell tabs can now be pinned via right-click; pinned tabs survive plugin reload and character
logout, keep their conversation history (loaded on demand from the message store on rehydrate), and
stay bound to the same `/tell` partner. A hard cap of 5 pinned tabs lives in a pool separate from
the 15-tab auto-tell pool, total ceiling 20. The sidebar groups pinned tabs into their own section
with a divider header, and the sidebar width itself is now configurable in **Theme & Layout**
between 44 and 160 px. Honorific glow outlines render when the title carries a Glow colour, opt-in
via **Settings → Integrations → Render glow outlines (Honorific)** (default off). Honorific's
gradient (Color3 / GradientColourSet / Wave / Pulse) is parsed but rendered statically — a later
cycle will port the full animation algorithm or land an upstream IPC PR for the resolved frame
colour. `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` — together they fix a Settings-Save regression where the chat input
could pop back to `/tell <pinned-partner>` after touching settings on a Party or Linkshell tab.
Internal items: `IPluginLogProxy` indirection over Dalamud's `IPluginLog` routes all ~91
`Plugin.Log` call sites through a testable proxy, closing the F12.1 test-isolation gap
(`MessageStore.Migrate0` runs in xUnit now). TempTab counter switched from `Interlocked` cached
field to derived `Tabs.Count(predicate)`. Migration v16 → v17 is additive (new `Tab.IsPinned` flag).
Build-Suite floor 688 → 710 (+22 tests across Pin-lifecycle predicates, pool limits, Tab.Clone
roundtrip, MessageStore Migrate0 construction, and Honorific TitleData JSON roundtrip).
## v1.4.6 — Code Hygiene and Refactor (released 2026-05-12)
Seventh sub-patch of the v1.4.x Polish Sweep series. Maintenance patch — no user-visible behaviour changes; tightens the
development feedback loop and pulls in two ChatTwo upstream bugfixes. `scripts/preflight.sh` gains a csharpier reflow
check (Block E) and a markdownlint pass (Block F), so style drift and markdown violations are blocked at the pre-push
gate. `FontManager.AddFontWithFallback` catch-filter now spans `InvalidOperationException` and `ArgumentException` on
top of the existing IO triad, with the exception type name in the warning log so the diagnostic path can see which
atlas-toolkit throw triggered the fallback. `BrandingLinks` and `IntegrationLinks` run a `[ModuleInitializer]` URL
validation pass on plugin load; a typo in a future URL rotation now throws at startup instead of failing silently when a
user clicks the broken button. Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the
native `Utf8String` when the linkshell check rejects the channel (rename to `IsChannelOrExistingLinkshell` plus
wrap-not-return), and `Tab.Clone` now deep-clones `UsedChannel` and `TellTarget` (the previous reference copy let PopOut
and Temp tabs mutate each other's channel state). The `ChatLogWindow` active-tab underline pill scales with
`ImGuiHelpers.GlobalScale` and rounds to physical pixels for crisp rendering above 100 % DPI. Internal items:
`HellionStyle` ChildBgAlpha extracted to a testable helper, `Plugin.SaveConfig` clones only the temp-tab subset in the
snapshot path, `SettingsOverview` caches the draw-list per frame, `Dalamud.Utility.Util` static surface routed through
an `IPlatformUtil` indirection (`MessageStore`'s `IsWine` probe is now testable in isolation). No schema bump, no
migration.
Seventh sub-patch of the v1.4.x Polish Sweep series. Maintenance patch — no user-visible behaviour
changes; tightens the development feedback loop and pulls in two ChatTwo upstream bugfixes.
`scripts/preflight.sh` gains a csharpier reflow check (Block E) and a markdownlint pass (Block F),
so style drift and markdown violations are blocked at the pre-push gate.
`FontManager.AddFontWithFallback` catch-filter now spans `InvalidOperationException` and
`ArgumentException` on top of the existing IO triad, with the exception type name in the warning log
so the diagnostic path can see which atlas-toolkit throw triggered the fallback. `BrandingLinks` and
`IntegrationLinks` run a `[ModuleInitializer]` URL validation pass on plugin load; a typo in a
future URL rotation now throws at startup instead of failing silently when a user clicks the broken
button. Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the native
`Utf8String` when the linkshell check rejects the channel (rename to `IsChannelOrExistingLinkshell`
plus wrap-not-return), and `Tab.Clone` now deep-clones `UsedChannel` and `TellTarget` (the previous
reference copy let PopOut and Temp tabs mutate each other's channel state). The `ChatLogWindow`
active-tab underline pill scales with `ImGuiHelpers.GlobalScale` and rounds to physical pixels for
crisp rendering above 100 % DPI. Internal items: `HellionStyle` ChildBgAlpha extracted to a testable
helper, `Plugin.SaveConfig` clones only the temp-tab subset in the snapshot path, `SettingsOverview`
caches the draw-list per frame, `Dalamud.Utility.Util` static surface routed through an
`IPlatformUtil` indirection (`MessageStore`'s `IsWine` probe is now testable in isolation). No
schema bump, no migration.
## v1.4.5 — UX and Robustness (released 2026-05-12)
Sixth sub-patch of the v1.4.x Polish Sweep series. User-visible robustness polish plus two doc/test polish items from
the audit backlog. Chat-log draw failures now surface as a one-shot notification instead of failing silently. The
first-run wizard splits accept from close: `OnClose` no longer silently sets `FirstRunCompleted`, and a new footer
"Later — keep defaults" button is the explicit path to dismiss without picking a profile. `InputHistoryService` clears
on plugin dispose so the previous session's typed commands don't bleed into the next load. `FontManager` falls back to
the system font path if the embedded Hellion font resource is missing (broken-csproj / dev-build only). The status bar
hides the version slot when the chat window is too narrow to fit all five slots without overlap. Plus
`Plugin.cs:167-168` gains an explicit session-only Auto-Tell-Tab invariant comment with a `TempTabCounter.InitFromList`
pin in the Build-Suite. No schema bump, no migration.
Sixth sub-patch of the v1.4.x Polish Sweep series. User-visible robustness polish plus two doc/test
polish items from the audit backlog. Chat-log draw failures now surface as a one-shot notification
instead of failing silently. The first-run wizard splits accept from close: `OnClose` no longer
silently sets `FirstRunCompleted`, and a new footer "Later — keep defaults" button is the explicit
path to dismiss without picking a profile. `InputHistoryService` clears on plugin dispose so the
previous session's typed commands don't bleed into the next load. `FontManager` falls back to the
system font path if the embedded Hellion font resource is missing (broken-csproj / dev-build only).
The status bar hides the version slot when the chat window is too narrow to fit all five slots
without overlap. Plus `Plugin.cs:167-168` gains an explicit session-only Auto-Tell-Tab invariant
comment with a `TempTabCounter.InitFromList` pin in the Build-Suite. No schema bump, no migration.
## v1.4.4 — Threading and IPC Safety Polish (released 2026-05-12)
Fifth sub-patch of the v1.4.x Polish Sweep series. `AutoTellTabsService.ActiveTempTabCount` switches from a
lock-protected LINQ `Count` to an `Interlocked` counter kept in sync from inside the existing mutation paths;
`Initialize()` seeds from the persisted Tabs list and `SaveConfig`'s snapshot-restore path calls a new
`ResyncTempTabCounter()` after the mid-step `RemoveAll`. `HonorificService` carries per-method threading banners and
`TryUnsubscribe`'s log level moves from Debug to Warning. `AutoTranslate.PreloadCache` is marked `IsBackground = true`
so plugin unload no longer waits for it. `Configuration.IsAllowedForStorage` logs once per unknown ChatType via a
`NonSerialized` `HashSet`, and `PrivacyPersistUnknownChannels` default flips to `true` for new installs. No schema bump,
no migration.
Fifth sub-patch of the v1.4.x Polish Sweep series. `AutoTellTabsService.ActiveTempTabCount` switches
from a lock-protected LINQ `Count` to an `Interlocked` counter kept in sync from inside the existing
mutation paths; `Initialize()` seeds from the persisted Tabs list and `SaveConfig`'s
snapshot-restore path calls a new `ResyncTempTabCounter()` after the mid-step `RemoveAll`.
`HonorificService` carries per-method threading banners and `TryUnsubscribe`'s log level moves from
Debug to Warning. `AutoTranslate.PreloadCache` is marked `IsBackground = true` so plugin unload no
longer waits for it. `Configuration.IsAllowedForStorage` logs once per unknown ChatType via a
`NonSerialized` `HashSet`, and `PrivacyPersistUnknownChannels` default flips to `true` for new
installs. No schema bump, no migration.
## v1.4.3 — Plugin-Load Async-Init + Repo-Cutover (released 2026-05-08)
Fourth and largest sub-patch of the v1.4.x Polish Sweep series. Plugin migrated to Dalamud's `IAsyncDalamudPlugin` API:
the constructor handles only bootstrap essentials (config load, language init, conflict detection); migrations, service
allocations, window construction and hook subscription move to `LoadAsync`. Schema gate replaces the v9 → v16 migration
chain; configs on schema v16+ load directly, older configs trigger an "install v1.4.2 first" error.
`AutoTranslate.PreloadCache` moved off the load path. `FontManager.BuildFonts` runs sync at the start of `LoadAsync`;
Dalamud rebuilds the font atlas on its own pipeline. Custom-repo URL cut over to `gitea.hellion-forge.cloud`; the GitHub
repo remains as a frozen v1.4.2 snapshot. Plugin load time sits at ~3.7 s median (5 reloads), comparable to v1.4.2 — the
async migration is a foundation for v1.4.4 lazy-init optimisations rather than an immediate user-perceived win.
Fourth and largest sub-patch of the v1.4.x Polish Sweep series. Plugin migrated to Dalamud's
`IAsyncDalamudPlugin` API: the constructor handles only bootstrap essentials (config load, language
init, conflict detection); migrations, service allocations, window construction and hook
subscription move to `LoadAsync`. Schema gate replaces the v9 → v16 migration chain; configs on
schema v16+ load directly, older configs trigger an "install v1.4.2 first" error.
`AutoTranslate.PreloadCache` moved off the load path. `FontManager.BuildFonts` runs sync at the
start of `LoadAsync`; Dalamud rebuilds the font atlas on its own pipeline. Custom-repo URL cut over
to `gitea.hellion-forge.cloud`; the GitHub repo remains as a frozen v1.4.2 snapshot. Plugin load
time sits at ~3.7 s median (5 reloads), comparable to v1.4.2 — the async migration is a foundation
for v1.4.4 lazy-init optimisations rather than an immediate user-perceived win.
## v1.4.2 — ChatLog Frame-Hot-Path (released 2026-05-08)
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations eliminated from the ChatLogWindow render path
and the settings status bar. Card-mode border loop in `DrawMessages` hoists five invariants into a pre-loop hoist;
`AutoTellTabTint` gets a per-tab cache via `TabTintCache` (separate validation keys per cache, no cross-invalidation);
status bar moves the cache-gate check before the aggregation and replaces LINQ `Sum`+`Count` with a single-pass foreach.
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations eliminated from the
ChatLogWindow render path and the settings status bar. Card-mode border loop in `DrawMessages`
hoists five invariants into a pre-loop hoist; `AutoTellTabTint` gets a per-tab cache via
`TabTintCache` (separate validation keys per cache, no cross-invalidation); status bar moves the
cache-gate check before the aggregation and replaces LINQ `Sum`+`Count` with a single-pass foreach.
## v1.4.1 — Theme Engine Performance (released 2026-05-08)
Second sub-patch of the v1.4.x Polish Sweep series. ABGR cache pre-computed on theme records; `HellionStyle.PushGlobal`
reads from the cache instead of converting per slot per frame. **~13 % render-time recovery** in smoke tests (plan
estimate of 26 % was conservative; real result ~1015 %). Custom-theme hot-reload survives transient file locks via
last-known-good snapshot. Plus: Synthwave Sunset as the tenth built-in, author credits consolidated under Hellion Forge,
Mint Grove + Forge Merchantman credited to Carla Beleandis as a community thanks.
Second sub-patch of the v1.4.x Polish Sweep series. ABGR cache pre-computed on theme records;
`HellionStyle.PushGlobal` reads from the cache instead of converting per slot per frame. **~13 %
render-time recovery** in smoke tests (plan estimate of 26 % was conservative; real result ~1015
%). Custom-theme hot-reload survives transient file locks via last-known-good snapshot. Plus:
Synthwave Sunset as the tenth built-in, author credits consolidated under Hellion Forge, Mint
Grove + Forge Merchantman credited to Carla Beleandis as a community thanks.
## v1.4.0 — Critical Lifecycle Fixes (released 2026-05-07)
First sub-patch of the v1.4.x Polish Sweep series. Seven P0 findings from audit passes 3 and 4 resolved: async-void
loads, missing `IsBackground` flags, `GC.Collect` in Dispose, deferred-save race and pre-v13 backup lookup for
`WindowOpacity`. No schema bumps, no user-facing behaviour changes other than reload and shutdown running noticeably
cleaner.
First sub-patch of the v1.4.x Polish Sweep series. Seven P0 findings from audit passes 3 and 4
resolved: async-void loads, missing `IsBackground` flags, `GC.Collect` in Dispose, deferred-save
race and pre-v13 backup lookup for `WindowOpacity`. No schema bumps, no user-facing behaviour
changes other than reload and shutdown running noticeably cleaner.
## v1.3.0 — Plugin Integrations: Honorific (released 2026-05-07)
First cycle of the plugin integrations roadmap. Honorific custom titles displayed in the chat header with auto-detect
and silent fallback. New Integrations settings tab. Pattern-setter for the five following cycles (Context Menu,
NotificationMaster, RP Status Block, ExtraChat, XIVIM).
First cycle of the plugin integrations roadmap. Honorific custom titles displayed in the chat header
with auto-detect and silent fallback. New Integrations settings tab. Pattern-setter for the five
following cycles (Context Menu, NotificationMaster, RP Status Block, ExtraChat, XIVIM).
Spec: [Plugin Integrations Overview](../Hellion%20Chat%20Plugin-Integrationen.md)
## v1.2.3 — Theme Expansion (released 2026-05-06)
Four new built-in themes: Night Blue, Indigo Violet, Forge Merchantman, Hellion Spectrum (Deuteran/Protan-safe). No
engine changes. See `docs/CHANGELOG.md`.
Four new built-in themes: Night Blue, Indigo Violet, Forge Merchantman, Hellion Spectrum
(Deuteran/Protan-safe). No engine changes. See `docs/CHANGELOG.md`.
(v1.2.2 was burned because the `repo.json` manifest was not bumped in sync on the first push — re-released as v1.2.3
with full manifest synchronisation.)
(v1.2.2 was burned because the `repo.json` manifest was not bumped in sync on the first push —
re-released as v1.2.3 with full manifest synchronisation.)
## v1.2.1 — Settings Cleanup (released 2026-05-06)
Settings re-sorted thematically (9 cards), 4 dead settings removed, auto-migration v15 → v16 without data loss.
Settings re-sorted thematically (9 cards), 4 dead settings removed, auto-migration v15 → v16 without
data loss.
## v1.2.0 — Layout Refresh (released 2026-05-05)
Top tabs refresh, sidebar tab icons, bottom status bar, card rows as default message render, auto-tell tab hashing.
Top tabs refresh, sidebar tab icons, bottom status bar, card rows as default message render,
auto-tell tab hashing.
## v1.1.0 — Theme Foundation (released 2026-05-05)
Theme engine with five built-in themes, settings card grid, custom themes via JSON, theme authoring docs. Plugin icon
updated to Hellion Forge hammer. See `docs/CHANGELOG.md` for details.
Theme engine with five built-in themes, settings card grid, custom themes via JSON, theme authoring
docs. Plugin icon updated to Hellion Forge hammer. See `docs/CHANGELOG.md` for details.
Items from the original v1.1.0 plan (ad-block / spam filter, receive-suppressed-tells toggle) were deferred in favour of
the theme engine — both items live on in the mid-term block.
Items from the original v1.1.0 plan (ad-block / spam filter, receive-suppressed-tells toggle) were
deferred in favour of the theme engine — both items live on in the mid-term block.
---
## Mid-Term (v1.4.x+)
- **Plugin Integrations Roadmap (Cycles 26)** — six plugin integrations planned; Honorific (Cycle 1) is live, followed
by Context Menu, NotificationMaster, RP Status Block, ExtraChat and XIVIM in their own cycles. Spec and cycle order in
- **Plugin Integrations Roadmap (Cycles 26)** — six plugin integrations planned; Honorific
(Cycle 1) is live, followed by Context Menu, NotificationMaster, RP Status Block, ExtraChat and
XIVIM in their own cycles. Spec and cycle order in
[Plugin Integrations Overview](../Hellion%20Chat%20Plugin-Integrationen.md).
- **Ad-Block / Spam Filter** — hybrid concept combining a lightweight built-in filter with optional `NoSoliciting` IPC
integration. Addresses ad-spam in public channels and tells. Deferred from the v1.1.0 plan.
- **Receive-Suppressed-Tells Toggle** — auto-tell tabs trigger even when a third-party plugin (e.g. XIVMessenger)
globally suppresses /tell display. Same hook layer as ad-block, so they are bundled.
- **Database Viewer Inline Search** — full-text search in the DB viewer via SQLite FTS5. Currently only date and channel
filters are available.
- **TempTell Persistence** — pin toggle on TempTell tabs so selected tells survive a relog. Tester request from Jingliu.
- **FontManager Async Refactor** — move `LoadGameSymFontAsync` out of the blocking plugin constructor. Fix cold-start
hitching on first plugin load (low severity; plugin is functional).
- **Separate Opacity Active vs. Inactive** — second slider for inactive window opacity. Upstream declines this; we can
decide differently here.
- **Failed-Tell Notification** — visible message on /tell failure (offline, restricted instance, blacklisted,
world-mismatch) instead of silent failure.
- **Per-Tab Sound Notification** — sound toggle and optionally a custom .wav per tab, with mute-in-combat option.
- **Ad-Block / Spam Filter** — hybrid concept combining a lightweight built-in filter with optional
`NoSoliciting` IPC integration. Addresses ad-spam in public channels and tells. Deferred from the
v1.1.0 plan.
- **Receive-Suppressed-Tells Toggle** — auto-tell tabs trigger even when a third-party plugin (e.g.
XIVMessenger) globally suppresses /tell display. Same hook layer as ad-block, so they are bundled.
- **Database Viewer Inline Search** — full-text search in the DB viewer via SQLite FTS5. Currently
only date and channel filters are available.
- **TempTell Persistence** — pin toggle on TempTell tabs so selected tells survive a relog. Tester
request from Jingliu.
- **FontManager Async Refactor** — move `LoadGameSymFontAsync` out of the blocking plugin
constructor. Fix cold-start hitching on first plugin load (low severity; plugin is functional).
- **Separate Opacity Active vs. Inactive** — second slider for inactive window opacity. Upstream
declines this; we can decide differently here.
- **Failed-Tell Notification** — visible message on /tell failure (offline, restricted instance,
blacklisted, world-mismatch) instead of silent failure.
- **Per-Tab Sound Notification** — sound toggle and optionally a custom .wav per tab, with
mute-in-combat option.
---
@@ -265,27 +295,28 @@ the theme engine — both items live on in the mid-term block.
### UX and Tab Management
- **Regex Tab Routing** — route plugin output spam into dedicated tabs, auto-sort tells from specific people. Clearly
scoped against ad-block: routing sorts into views, blocking hides globally.
- **Regex Tab Routing** — route plugin output spam into dedicated tabs, auto-sort tells from
specific people. Clearly scoped against ad-block: routing sorts into views, blocking hides
globally.
- **Auto-Detect Duties** — tab switch on duty start via condition flag.
- **UX Bundle** — vertical tab bar as a layout option, Shift+Mousewheel to scroll tab headers without activating them,
global hotkey to close the active tab.
- **Configure Tab Title** — configurable tab title format (name / name + abbreviated world / full name / custom),
overridable per tab.
- **Name Display Options** — analogous to FFXIV vanilla (full name, first name abbreviated, initials), per-channel
override possible.
- **Item & Flag Linking** — outgoing: Shift-click on an item/flag sends it to the focused plugin input. Incoming: item
links and map coordinates are clickable.
- **Color Currently Selected Input Channel** — tint the channel-selector button in the input bar with the current
channel colour.
- **Plugin-Disclosure Pre-Send Filter** — configurable word/regex list blocks sending with a pre-send confirmation.
Protects against accidentally mentioning plugins in public channels.
- **Chat Clear on Name Change** — on character name change, migrate or wipe local history; default is wipe for maximum
privacy.
- **UX Bundle** — vertical tab bar as a layout option, Shift+Mousewheel to scroll tab headers
without activating them, global hotkey to close the active tab.
- **Configure Tab Title** — configurable tab title format (name / name + abbreviated world / full
name / custom), overridable per tab.
- **Name Display Options** — analogous to FFXIV vanilla (full name, first name abbreviated,
initials), per-channel override possible.
- **Item & Flag Linking** — outgoing: Shift-click on an item/flag sends it to the focused plugin
input. Incoming: item links and map coordinates are clickable.
- **Color Currently Selected Input Channel** — tint the channel-selector button in the input bar
with the current channel colour.
- **Plugin-Disclosure Pre-Send Filter** — configurable word/regex list blocks sending with a
pre-send confirmation. Protects against accidentally mentioning plugins in public channels.
- **Chat Clear on Name Change** — on character name change, migrate or wipe local history; default
is wipe for maximum privacy.
- **Hide Plugin Window on NG+ Screen** — extend hide logic to cover additional addon names.
- **Kick from Novice Network** — mentor niche; context menu entry with confirmation.
- **Text-to-Speech for /tell** — incoming tells via TTS, optionally per sender, with channel filter and mute-in-combat.
Low priority.
- **Text-to-Speech for /tell** — incoming tells via TTS, optionally per sender, with channel filter
and mute-in-combat. Low priority.
### Distribution and Branding
@@ -297,24 +328,29 @@ the theme engine — both items live on in the mid-term block.
## Bug Verifications
Carried over from the upstream issue tracker; not yet reproduced or verified in Hellion Chat 1.0.0. Will be tested
against the current state when opportunity allows.
Carried over from the upstream issue tracker; not yet reproduced or verified in Hellion Chat 1.0.0.
Will be tested against the current state when opportunity allows.
- **Right-Click Whisper Error** in Field Ops / Special Instances (Eureka, Bozja, Occult Crescent, DRS) — upstream
[#168](https://github.com/Infiziert90/ChatTwo/issues/168). Reply helper appears to swallow the `@World` suffix.
- **FPS Drops with Plugin Active** — upstream [#145](https://github.com/Infiziert90/ChatTwo/issues/145). 1020 % drop
since upstream v1.29.19.0. v1.0.0 includes several fixes on the suspected paths; repro test against the current state
is open.
- **Add Blacklist from Plugin Window** — upstream [#140](https://github.com/Infiziert90/ChatTwo/issues/140). Right-click
add-to-blacklist throws "Cannot locate character with that name"; works via vanilla chat.
- **DB Viewer Column Sort** — State column sorts lexicographically instead of numerically (10 before 2). XIVIM
[#82](https://github.com/NightmareXIV/XIVInstantMessenger/issues/82); repro in Hellion Chat open.
- **Right-Click Whisper Error** in Field Ops / Special Instances (Eureka, Bozja, Occult Crescent,
DRS) — upstream [#168](https://github.com/Infiziert90/ChatTwo/issues/168). Reply helper appears to
swallow the `@World` suffix.
- **FPS Drops with Plugin Active** — upstream
[#145](https://github.com/Infiziert90/ChatTwo/issues/145). 1020 % drop since upstream v1.29.19.0.
v1.0.0 includes several fixes on the suspected paths; repro test against the current state is
open.
- **Add Blacklist from Plugin Window** — upstream
[#140](https://github.com/Infiziert90/ChatTwo/issues/140). Right-click add-to-blacklist throws
"Cannot locate character with that name"; works via vanilla chat.
- **DB Viewer Column Sort** — State column sorts lexicographically instead of numerically (10 before
2). XIVIM [#82](https://github.com/NightmareXIV/XIVInstantMessenger/issues/82); repro in Hellion
Chat open.
---
## Licence Boundary
Hellion Chat is licensed under EUPL-1.2. Concept imports from AGPL-3.0 plugins (e.g. XIV Instant Messenger) are
architectural inspiration only — no code was ported. Code imports from the upstream codebase are complete as of v1.4.x
because Chat 2 is undergoing a fundamental rework and selective patches are no longer cleanly portable. Status and
rationale in [`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
Hellion Chat is licensed under EUPL-1.2. Concept imports from AGPL-3.0 plugins (e.g. XIV Instant
Messenger) are architectural inspiration only — no code was ported. Code imports from the upstream
codebase are complete as of v1.4.x because Chat 2 is undergoing a fundamental rework and selective
patches are no longer cleanly portable. Status and rationale in
[`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
+41 -33
View File
@@ -4,8 +4,9 @@
# Theme Authoring Guide
> Built by **Hellion Forge** — the plugin workshop arm of [Hellion Online Media](https://hellion-media.de). HellionChat
> ships with nine built-in themes; this guide walks you through writing your own.
> Built by **Hellion Forge** — the plugin workshop arm of
> [Hellion Online Media](https://hellion-media.de). HellionChat ships with nine built-in themes;
> this guide walks you through writing your own.
## TL;DR
@@ -23,10 +24,11 @@ That's the whole loop. The rest of this document is reference.
%APPDATA%\XIVLauncher\pluginConfigs\HellionChat\themes\
```
(or the equivalent path on Linux/macOS — Settings → Themes → "Open themes folder" opens it directly).
(or the equivalent path on Linux/macOS — Settings → Themes → "Open themes folder" opens it
directly).
Each `*.json` file in this folder is loaded as one theme. The `example-theme.json` that HellionChat seeds on first
launch is your starting template.
Each `*.json` file in this folder is loaded as one theme. The `example-theme.json` that HellionChat
seeds on first launch is your starting template.
## File format
@@ -60,7 +62,8 @@ Theme JSON has four blocks:
### Color slots
All values are 6-digit `#RRGGBB` or 8-digit `#RRGGBBAA` hex strings. Six-digit values get an implicit `FF` alpha.
All values are 6-digit `#RRGGBB` or 8-digit `#RRGGBBAA` hex strings. Six-digit values get an
implicit `FF` alpha.
| Slot | Role |
| ---------------------------- | ------------------------------------------------------------------------------- |
@@ -87,7 +90,8 @@ All values are 6-digit `#RRGGBB` or 8-digit `#RRGGBBAA` hex strings. Six-digit v
### Layout slots
All values are floats in pixels. `BorderSize` is 0 or 1 (no thicker borders look right with ImGui's edge anti-aliasing).
All values are floats in pixels. `BorderSize` is 0 or 1 (no thicker borders look right with ImGui's
edge anti-aliasing).
| Slot | Typical range | Notes |
| ------------------- | ------------- | ------------------------------------------------- |
@@ -103,8 +107,8 @@ All values are floats in pixels. `BorderSize` is 0 or 1 (no thicker borders look
### Optional `chatChannels`
If present, your theme proposes its own chat-channel colors. Property names are `ChatType` enum values
(case-insensitive). Unknown names are skipped silently — safe for forward-compat.
If present, your theme proposes its own chat-channel colors. Property names are `ChatType` enum
values (case-insensitive). Unknown names are skipped silently — safe for forward-compat.
```json
"chatChannels": {
@@ -120,8 +124,9 @@ If present, your theme proposes its own chat-channel colors. Property names are
}
```
The user is asked **once per theme switch** whether to apply these colors — never auto-overwriting existing picks. The
banner shows up only if your suggested colors differ from the user's current `Configuration.ChatColours`.
The user is asked **once per theme switch** whether to apply these colors — never auto-overwriting
existing picks. The banner shows up only if your suggested colors differ from the user's current
`Configuration.ChatColours`.
#### Channel-identity rule
@@ -137,25 +142,26 @@ banner shows up only if your suggested colors differ from the user's current `Co
| FreeCompany | cyan-teal | Guild ops. |
| NoviceNetwork | lime-green | Mentor channel. |
A theme can tint these toward its brand family (e.g., a purple theme can shift Tell from `#FF99CC` to `#E090FF`), but
**don't** flip them (Tell suddenly green, Yell suddenly cyan). RP groups and combat-spec setups depend on the visual
hierarchy.
A theme can tint these toward its brand family (e.g., a purple theme can shift Tell from `#FF99CC`
to `#E090FF`), but **don't** flip them (Tell suddenly green, Yell suddenly cyan). RP groups and
combat-spec setups depend on the visual hierarchy.
The eight colored built-in themes (Hellion Arctic, Hellion Spectrum, Event Horizon, Crystal Nocturne, Mint Grove, Night
Blue, Indigo Violet, Forge Merchantman) all follow this rule — read their source for reference. Chat 2 Klassik
intentionally ships without `chatChannels` so the user keeps their existing picks.
The eight colored built-in themes (Hellion Arctic, Hellion Spectrum, Event Horizon, Crystal
Nocturne, Mint Grove, Night Blue, Indigo Violet, Forge Merchantman) all follow this rule — read
their source for reference. Chat 2 Klassik intentionally ships without `chatChannels` so the user
keeps their existing picks.
## Theme families
Naming convention `<color>-<modifier>` is recommended for theme families. The first member of a family is the
lightest/brightest:
Naming convention `<color>-<modifier>` is recommended for theme families. The first member of a
family is the lightest/brightest:
- `mint-grove` (current built-in, light mint)
- `forest-grove` (planned, dark emerald)
- `moss-grove` (planned, mid muted)
Code-wise families have no special handling — only the slug naming hints at the relationship. The picker may group
families later, but that's not required.
Code-wise families have no special handling — only the slug naming hints at the relationship. The
picker may group families later, but that's not required.
## Validation and errors
@@ -164,8 +170,8 @@ When HellionChat loads your theme:
- **Schema mismatch** (`schemaVersion != 1`): theme is skipped, warning written to `/xllog`.
- **Missing required field** (e.g., no `slug`): theme is skipped, warning written.
- **Invalid hex** (e.g., `#GGHHII`): theme is skipped, warning written.
- **Unknown channel name** in `chatChannels`: that one channel is skipped silently, the rest of the theme loads
normally.
- **Unknown channel name** in `chatChannels`: that one channel is skipped silently, the rest of the
theme loads normally.
Check `/xllog` after a plugin reload to see what loaded and what didn't.
@@ -177,23 +183,25 @@ Check `/xllog` after a plugin reload to see what loaded and what didn't.
4. Watch every plugin window (chat, settings, pop-out) and pick something to fix.
5. Tweak. Reload. Repeat.
Tip: the **Settings → Themes** picker shows a mini-mockup per theme — your colors are visible before you switch.
Tip: the **Settings → Themes** picker shows a mini-mockup per theme — your colors are visible before
you switch.
## Sharing themes
Themes are JSON, so sharing is just a file. Drop it into someone's `pluginConfigs/HellionChat/themes/` folder and their
plugin picks it up on next reload.
Themes are JSON, so sharing is just a file. Drop it into someone's
`pluginConfigs/HellionChat/themes/` folder and their plugin picks it up on next reload.
A community theme repository is on the Hellion Forge roadmap. Until then: share via Discord or any pastebin.
A community theme repository is on the Hellion Forge roadmap. Until then: share via Discord or any
pastebin.
## Reference
- `docs/example-theme.json` (seeded automatically on first launch into `pluginConfigs/HellionChat/themes/`) — minimal
valid theme.
- The five built-in themes live in source under `HellionChat/Themes/Builtin/`. They are a good reference for Color
choices that work.
- [Hellion Online Media branding](https://hellion-media.de) — the Arctic Cyan + Ember Glow palette that drives the
default Hellion Arctic theme.
- `docs/example-theme.json` (seeded automatically on first launch into
`pluginConfigs/HellionChat/themes/`) — minimal valid theme.
- The five built-in themes live in source under `HellionChat/Themes/Builtin/`. They are a good
reference for Color choices that work.
- [Hellion Online Media branding](https://hellion-media.de) — the Arctic Cyan + Ember Glow palette
that drives the default Hellion Arctic theme.
---
+22 -20
View File
@@ -1,7 +1,7 @@
# Third-party notices
HellionChat ships and depends on a number of third-party components. This document lists them, their licences and which
of them touch the network. It is the inventory referenced by `PRIVACY.md`.
HellionChat ships and depends on a number of third-party components. This document lists them, their
licences and which of them touch the network. It is the inventory referenced by `PRIVACY.md`.
Last reviewed: 2026-05-05 (HellionChat v1.1.0).
@@ -20,11 +20,11 @@ Pinned in `HellionChat/HellionChat.csproj`. Versions reflect the v1.1.0 build.
| [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) | 3.1.12 | [Six Labors Split License 1.0](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) (OSI-approved; free for open-source / non-commercial use, commercial licence required for closed-source commercial use) | no | Image decoding for cached emotes. |
| [SQLitePCLRaw.lib.e_sqlite3](https://github.com/ericsink/SQLitePCL.raw) | 3.50.3 | MIT | no | Native SQLite binary, explicitly pinned to override the transitive default for CVE-2025-6965 (memory corruption from aggregate-term overflow) and CVE-2025-7709. |
Six Labors note: HellionChat is an EUPL-1.2-licensed open-source project distributed at no cost. Use of ImageSharp 3.x
under the Six Labors Split License 1.0 is permitted on that basis. Anyone forking HellionChat for closed-source or
commercial redistribution should review the
[Six Labors licence terms](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) and obtain a commercial licence if
required.
Six Labors note: HellionChat is an EUPL-1.2-licensed open-source project distributed at no cost. Use
of ImageSharp 3.x under the Six Labors Split License 1.0 is permitted on that basis. Anyone forking
HellionChat for closed-source or commercial redistribution should review the
[Six Labors licence terms](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) and obtain a
commercial licence if required.
## SDK and tooling
@@ -44,23 +44,24 @@ required.
## Upstream code
HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infiziert90 (Infi) and Anna Clemens, also
licensed under EUPL-1.2. The bulk of the code, including the message store architecture, the channel logic, the hook
system and the ImGui chat window, originates from upstream. See `../NOTICE.md` for the attribution; `UPSTREAM_SYNC.md`
documents the upstream-sync history, including the close of active cherry-picking in the v1.4.x cycle.
HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infiziert90 (Infi) and
Anna Clemens, also licensed under EUPL-1.2. The bulk of the code, including the message store
architecture, the channel logic, the hook system and the ImGui chat window, originates from
upstream. See `../NOTICE.md` for the attribution; `UPSTREAM_SYNC.md` documents the upstream-sync
history, including the close of active cherry-picking in the v1.4.x cycle.
---
## Components that touch the network
Of everything listed above, **none** of the bundled or NuGet components opens network connections on their own. All
outbound traffic is initiated explicitly by HellionChat's own source files and is documented in `PRIVACY.md` under
"Outbound network calls":
Of everything listed above, **none** of the bundled or NuGet components opens network connections on
their own. All outbound traffic is initiated explicitly by HellionChat's own source files and is
documented in `PRIVACY.md` under "Outbound network calls":
- `HellionChat/EmoteCache.cs` → BetterTTV API + CDN (opt-out via setting)
The earlier Square Enix Lodestone font download (`FontManager.cs`) was removed in v1.0.4 — it was a leftover from
upstream's removed webinterface feature and was no longer consumed.
The earlier Square Enix Lodestone font download (`FontManager.cs`) was removed in v1.0.4 — it was a
leftover from upstream's removed webinterface feature and was no longer consumed.
---
@@ -72,8 +73,9 @@ To regenerate the dependency inventory after a version bump:
dotnet list HellionChat.sln package --include-transitive
```
The "direct NuGet dependencies" table above only lists direct references. Transitive dependencies pulled in by Dalamud
SDK or by the listed packages are covered by the SDK / package licences and documented by their respective maintainers.
The "direct NuGet dependencies" table above only lists direct references. Transitive dependencies
pulled in by Dalamud SDK or by the listed packages are covered by the SDK / package licences and
documented by their respective maintainers.
To re-audit the network-call inventory:
@@ -82,5 +84,5 @@ grep -rn -E "HttpClient|HttpRequest|new Uri\(|https?://" \
--include="*.cs" HellionChat/
```
Any new hit that is not a click-through (`Util.OpenLink`) or a payload-parsing call must be added to `PRIVACY.md` before
release.
Any new hit that is not a click-through (`Util.OpenLink`) or a payload-parsing call must be added to
`PRIVACY.md` before release.
+66 -56
View File
@@ -1,93 +1,103 @@
# Upstream Sync
HellionChat is a standalone EUPL-1.2 plugin that originated from [Chat 2](https://github.com/Infiziert90/ChatTwo). Since
v1.0.0 it lives under its own namespace, IPC channels and source tree. The active cherry-pick pipeline from upstream
Chat 2 is closed since the v1.4.x cycle.
HellionChat is a standalone EUPL-1.2 plugin that originated from
[Chat 2](https://github.com/Infiziert90/ChatTwo). Since v1.0.0 it lives under its own namespace, IPC
channels and source tree. The active cherry-pick pipeline from upstream Chat 2 is closed since the
v1.4.x cycle.
This document covers what that means, why I closed it, and what stays in place.
## A Word on Intent
HellionChat is not trying to replace Chat 2. I build it for myself, and maybe for people who want the same things I do:
a privacy-first chat plugin with tighter defaults and no remote-access surface. If that is not you, Chat 2 is the better
choice and a well-maintained project.
HellionChat is not trying to replace Chat 2. I build it for myself, and maybe for people who want
the same things I do: a privacy-first chat plugin with tighter defaults and no remote-access
surface. If that is not you, Chat 2 is the better choice and a well-maintained project.
I am available to Infi if he ever has questions about HellionChat or how I have diverged from the upstream code. What I
will not do is interfere with Chat 2's direction or push unsolicited opinions into his project.
I am available to Infi if he ever has questions about HellionChat or how I have diverged from the
upstream code. What I will not do is interfere with Chat 2's direction or push unsolicited opinions
into his project.
Long-term compatibility between Chat 2 and HellionChat is not guaranteed and, frankly, not technically possible. I am
building a new UI from scratch and making deliberate architectural decisions that pull in a different direction. Some
upstream patches will simply stop applying cleanly and that is expected.
Long-term compatibility between Chat 2 and HellionChat is not guaranteed and, frankly, not
technically possible. I am building a new UI from scratch and making deliberate architectural
decisions that pull in a different direction. Some upstream patches will simply stop applying
cleanly and that is expected.
## Why Cherry-Picking Stopped in v1.4.x
Two things converged:
1. **Chat 2 is in a rework cycle.** Infi mentioned directly that parts of ChatTwo are being reworked and "stuff may not
be able to be cherry picked anymore." Once the upstream code paths I would pull from no longer exist in the same
shape, `git cherry-pick` stops being a meaningful tool — what would land would not be the change Infi wrote, it would
be a hand-port of his concept.
2. **HellionChat has drifted enough that selective patches require adaptation anyway.** The UI is being rebuilt, the
theme engine sits on top of HellionStyle which has no upstream equivalent, the privacy filter changes how messages
flow through MessageManager. Even before the rework was announced, more and more upstream patches needed adaptation
rather than a clean apply.
1. **Chat 2 is in a rework cycle.** Infi mentioned directly that parts of ChatTwo are being reworked
and "stuff may not be able to be cherry picked anymore." Once the upstream code paths I would
pull from no longer exist in the same shape, `git cherry-pick` stops being a meaningful tool —
what would land would not be the change Infi wrote, it would be a hand-port of his concept.
2. **HellionChat has drifted enough that selective patches require adaptation anyway.** The UI is
being rebuilt, the theme engine sits on top of HellionStyle which has no upstream equivalent, the
privacy filter changes how messages flow through MessageManager. Even before the rework was
announced, more and more upstream patches needed adaptation rather than a clean apply.
Together those two points mean continuing to call this an "active cherry-pick pipeline" was no longer honest. So I
closed it.
Together those two points mean continuing to call this an "active cherry-pick pipeline" was no
longer honest. So I closed it.
## What Closing the Pipeline Means in Practice
- The `upstream` git remote was removed locally on 2026-05-08. Anyone setting up a fresh clone does **not** add it back.
- New commits will not carry `(cherry picked from commit ...)` trailers. Anything that originates from Chat 2 from this
point forward will be a hand-port at most, and it gets called out as such in its own commit message and in the
relevant source comments.
- The existing cherry-pick trail stays in the git history exactly as it is. Every `(cherry picked from commit ...)` line
that was added with `-x` in earlier releases remains intact; that is the attribution paper trail and removing it would
be wrong.
- The `upstream` git remote was removed locally on 2026-05-08. Anyone setting up a fresh clone does
**not** add it back.
- New commits will not carry `(cherry picked from commit ...)` trailers. Anything that originates
from Chat 2 from this point forward will be a hand-port at most, and it gets called out as such in
its own commit message and in the relevant source comments.
- The existing cherry-pick trail stays in the git history exactly as it is. Every
`(cherry picked from commit ...)` line that was added with `-x` in earlier releases remains
intact; that is the attribution paper trail and removing it would be wrong.
## What Does Not Change
- **EUPL-1.2 anchor lines in source files.** Files that originated from Chat 2 keep their licence headers and any "based
on Infiziert90/ChatTwo" notice exactly as they are. The licence obligations under EUPL-1.2 do not lapse because
cherry-picking stopped.
- **NOTICE.md** stays canonical. Attribution to Infi and Anna for the message store, channel logic, hook system, ImGui
chat window and the localisation infrastructure remains the foundation statement of this fork.
- **README acknowledgements.** The Acknowledgements section in `README.md`, the maintainer thanks in the About tab, and
the `Language.*.resx` Crowdin translator credit list all stay as they are.
- **The original `Language.*.resx` files** remain in the source tree in their last upstream-sync state. They are the
work of the Chat 2 Crowdin community and the existing translations stay valuable. They will not receive automatic
upstream updates anymore — see CONTRIBUTING.md for what that means for translators.
- **EUPL-1.2 anchor lines in source files.** Files that originated from Chat 2 keep their licence
headers and any "based on Infiziert90/ChatTwo" notice exactly as they are. The licence obligations
under EUPL-1.2 do not lapse because cherry-picking stopped.
- **NOTICE.md** stays canonical. Attribution to Infi and Anna for the message store, channel logic,
hook system, ImGui chat window and the localisation infrastructure remains the foundation
statement of this fork.
- **README acknowledgements.** The Acknowledgements section in `README.md`, the maintainer thanks in
the About tab, and the `Language.*.resx` Crowdin translator credit list all stay as they are.
- **The original `Language.*.resx` files** remain in the source tree in their last upstream-sync
state. They are the work of the Chat 2 Crowdin community and the existing translations stay
valuable. They will not receive automatic upstream updates anymore — see CONTRIBUTING.md for what
that means for translators.
## What Could Re-Open Later
If Chat 2's rework lands and stabilises, and there is a piece of upstream code that I genuinely want in HellionChat, the
path forward is **study and re-implement**, not cherry-pick. That means:
If Chat 2's rework lands and stabilises, and there is a piece of upstream code that I genuinely want
in HellionChat, the path forward is **study and re-implement**, not cherry-pick. That means:
- Read the upstream change, understand the design, port the concept to HellionChat's actual code paths.
- Credit the upstream author in the commit message and, if the ported code is non-trivial, in a source-file comment.
- Read the upstream change, understand the design, port the concept to HellionChat's actual code
paths.
- Credit the upstream author in the commit message and, if the ported code is non-trivial, in a
source-file comment.
- Pre-clear with Infi if the port is large enough to warrant a conversation.
This is heavier than `git cherry-pick -x` and that is the point. Cherry-picking was light because both codebases shared
structure; once they do not, the proper attribution costs a real conversation rather than a flag on a git command.
This is heavier than `git cherry-pick -x` and that is the point. Cherry-picking was light because
both codebases shared structure; once they do not, the proper attribution costs a real conversation
rather than a flag on a git command.
## Contributing Back
HellionChat benefits from Chat 2's work, so I try to give something back where I can. If I fix a bug or improve
something that would be useful to Chat 2 and is not HellionChat-specific, I submit a good-will PR to
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo).
HellionChat benefits from Chat 2's work, so I try to give something back where I can. If I fix a bug
or improve something that would be useful to Chat 2 and is not HellionChat-specific, I submit a
good-will PR to [Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo).
A few things to note about that process:
- Good-will PRs are validated in a separate fork first to make sure the fix stands on its own without HellionChat
context.
- They are written by hand. No AI-generated code goes to Infi's project. He did not ask for Pair-level AI involvement
and I will not push that decision onto his codebase.
- This is not guaranteed for every change, only where it makes sense and where I am confident the fix is clean and
self-contained.
- Good-will PRs are validated in a separate fork first to make sure the fix stands on its own
without HellionChat context.
- They are written by hand. No AI-generated code goes to Infi's project. He did not ask for
Pair-level AI involvement and I will not push that decision onto his codebase.
- This is not guaranteed for every change, only where it makes sense and where I am confident the
fix is clean and self-contained.
- Whether it gets accepted is Infi's call, and a "no" is fine.
## When Upstream Takes a Direction I Cannot Follow
If a future Chat 2 release breaks compatibility with the HellionChat privacy philosophy in a way that cannot be resolved
(mandatory cloud sync, removal of the local message store, an incompatible licence change), HellionChat continues from
where it is. The inherited history stays under EUPL-1.2 and stays attributed.
If a future Chat 2 release breaks compatibility with the HellionChat privacy philosophy in a way
that cannot be resolved (mandatory cloud sync, removal of the local message store, an incompatible
licence change), HellionChat continues from where it is. The inherited history stays under EUPL-1.2
and stays attributed.