diff --git a/.editorconfig b/.editorconfig
index 78ee905..4179409 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,167 +1,252 @@
-[*]
-indent_style=space
-tab_width=4
-indent_size=4
-trim_trailing_whitespace=true
-insert_final_newline=false
+# ##############################################################
+# #
+# # .editorconfig – Hellion Forge / Hellion Media
+# #
+# # Überarbeitet: Mai 2026
+# #
+# # Strategie:
+# # - Standard-.NET-Conventions (private Fields = _camelCase)
+# # - CSharpier übernimmt die meiste Formatierung
+# # - Hier: Naming, IDE-Hints, Backup-Format-Regeln
+# #
+# # ##############################################################
-# JetBrains Rider custom properties for code formatting styles
-resharper_csharp_brace_style=next_line
-# Allman für standard Tooling (VS Code)
+root = true
+
+
+# =====================================================
+# Defaults (alle Files)
+# =====================================================
+
+[*]
+indent_style = space
+tab_width = 4
+indent_size = 4
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+
+# =====================================================
+# Markdown: Trailing Spaces erlaubt (2 Spaces =
)
+# =====================================================
+
+[*.md]
+trim_trailing_whitespace = false
+
+
+# =====================================================
+# JSON / YAML / Web-Configs: 2-Space-Indent
+# Konsistent mit yamllint und Prettier-Override
+# =====================================================
+
+[*.{yaml,yml}]
+indent_size = 2
+
+[*.{json,jsonc,har,jsb2,jsb3,postman_collection,postman_environment}]
+indent_size = 2
+
+[{.babelrc,.eslintrc,.prettierrc,.markdownlintrc,.stylelintrc,bowerrc}]
+indent_size = 2
+
+
+# =====================================================
+# .NET / XAML / Razor / Resources: 4-Space-Indent
+# =====================================================
+
+[*.{cs,csx,vb,fs,fsi,fsx}]
+indent_size = 4
+
+[*.{xml,xsd,xaml,axaml,paml,resx,resw,nuspec,config}]
+indent_size = 4
+
+[*.{cshtml,razor,aspx,ascx,asax,master,axaml}]
+indent_size = 4
+
+
+# ##############################################################
+# #
+# # C# Sektion: Style, Naming, Format
+# #
+# ##############################################################
+
+[*.{cs,csx}]
+
+
+# =====================================================
+# C# Style – var-Präferenz
+# =====================================================
+
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+
+# =====================================================
+# C# Style – Sonstiges
+# =====================================================
+
+# UTF-8 String Literals (C# 11+)
+csharp_style_prefer_utf8_string_literals = true:suggestion
+
+# Reihenfolge der Access-Modifier (Microsoft-Empfehlung)
+csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
+
+# Initializer: nicht alles auf eine Zeile
+csharp_new_line_before_members_in_object_initializers = false
+
+
+# =====================================================
+# C# Format – Braces (Backup, falls CSharpier nicht läuft)
+# =====================================================
+
+# Allman Style: Klammern auf neue Zeile
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
-# Switch-Einrückung
+
+# =====================================================
+# C# Format – Switch-Einrückung
+# =====================================================
+
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
-resharper_csharp_braces_for_foreach=not_required
-resharper_csharp_braces_for_for=not_required
-resharper_csharp_braces_for_while=not_required
-charset=utf-8
-end_of_line=lf
-# Microsoft .NET properties
-csharp_new_line_before_members_in_object_initializers=false
-csharp_preferred_modifier_order=public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
-csharp_style_prefer_utf8_string_literals=true:suggestion
-csharp_style_var_elsewhere=true:suggestion
-csharp_style_var_for_built_in_types=true:suggestion
-csharp_style_var_when_type_is_apparent=true:suggestion
-dotnet_naming_rule.private_constants_rule.import_to_resharper=True
-dotnet_naming_rule.private_constants_rule.resharper_description=Constant fields (private)
-dotnet_naming_rule.private_constants_rule.resharper_guid=236f7aa5-7b06-43ca-bf2a-9b31bfcff09a
-dotnet_naming_rule.private_constants_rule.severity=warning
-dotnet_naming_rule.private_constants_rule.style=upper_camel_case_style
-dotnet_naming_rule.private_constants_rule.symbols=private_constants_symbols
-dotnet_naming_rule.private_instance_fields_rule.import_to_resharper=True
-dotnet_naming_rule.private_instance_fields_rule.resharper_description=Instance fields (private)
-dotnet_naming_rule.private_instance_fields_rule.resharper_guid=4a98fdf6-7d98-4f5a-afeb-ea44ad98c70c
-dotnet_naming_rule.private_instance_fields_rule.severity=warning
-dotnet_naming_rule.private_instance_fields_rule.style=upper_camel_case_style
-dotnet_naming_rule.private_instance_fields_rule.symbols=private_instance_fields_symbols
-dotnet_naming_rule.private_instance_fields_rule_1.import_to_resharper=True
-dotnet_naming_rule.private_instance_fields_rule_1.resharper_description=Instance fields (private)
-dotnet_naming_rule.private_instance_fields_rule_1.resharper_guid=4a98fdf6-7d98-4f5a-afeb-ea44ad98c70c
-dotnet_naming_rule.private_instance_fields_rule_1.severity=warning
-dotnet_naming_rule.private_instance_fields_rule_1.style=upper_camel_case_style
-dotnet_naming_rule.private_instance_fields_rule_1.symbols=private_instance_fields_symbols_1
-dotnet_naming_rule.private_static_fields_rule.import_to_resharper=True
-dotnet_naming_rule.private_static_fields_rule.resharper_description=Static fields (private)
-dotnet_naming_rule.private_static_fields_rule.resharper_guid=f9fce829-e6f4-4cb2-80f1-5497c44f51df
-dotnet_naming_rule.private_static_fields_rule.severity=warning
-dotnet_naming_rule.private_static_fields_rule.style=upper_camel_case_style
-dotnet_naming_rule.private_static_fields_rule.symbols=private_static_fields_symbols
-dotnet_naming_rule.private_static_readonly_rule.import_to_resharper=True
-dotnet_naming_rule.private_static_readonly_rule.resharper_description=Static readonly fields (private)
-dotnet_naming_rule.private_static_readonly_rule.resharper_guid=15b5b1f1-457c-4ca6-b278-5615aedc07d3
-dotnet_naming_rule.private_static_readonly_rule.severity=warning
-dotnet_naming_rule.private_static_readonly_rule.style=upper_camel_case_style
-dotnet_naming_rule.private_static_readonly_rule.symbols=private_static_readonly_symbols
-dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper=True
-dotnet_naming_rule.unity_serialized_field_rule.resharper_description=Unity serialized field
-dotnet_naming_rule.unity_serialized_field_rule.resharper_guid=5f0fdb63-c892-4d2c-9324-15c80b22a7ef
-dotnet_naming_rule.unity_serialized_field_rule.severity=warning
-dotnet_naming_rule.unity_serialized_field_rule.style=lower_camel_case_style_1
-dotnet_naming_rule.unity_serialized_field_rule.symbols=unity_serialized_field_symbols
-dotnet_naming_rule.unity_serialized_field_rule_1.import_to_resharper=True
-dotnet_naming_rule.unity_serialized_field_rule_1.resharper_description=Unity serialized field
-dotnet_naming_rule.unity_serialized_field_rule_1.resharper_guid=5f0fdb63-c892-4d2c-9324-15c80b22a7ef
-dotnet_naming_rule.unity_serialized_field_rule_1.severity=warning
-dotnet_naming_rule.unity_serialized_field_rule_1.style=lower_camel_case_style_1
-dotnet_naming_rule.unity_serialized_field_rule_1.symbols=unity_serialized_field_symbols_1
-dotnet_naming_style.lower_camel_case_style.capitalization=camel_case
-dotnet_naming_style.lower_camel_case_style.required_prefix=_
-dotnet_naming_style.lower_camel_case_style_1.capitalization=camel_case
-dotnet_naming_style.upper_camel_case_style.capitalization=pascal_case
-dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities=private
-dotnet_naming_symbols.private_constants_symbols.applicable_kinds=field
-dotnet_naming_symbols.private_constants_symbols.required_modifiers=const
-dotnet_naming_symbols.private_constants_symbols.resharper_applicable_kinds=constant_field
-dotnet_naming_symbols.private_constants_symbols.resharper_required_modifiers=any
-dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities=private
-dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds=field
-dotnet_naming_symbols.private_instance_fields_symbols.resharper_applicable_kinds=field,readonly_field
-dotnet_naming_symbols.private_instance_fields_symbols.resharper_required_modifiers=instance
-dotnet_naming_symbols.private_instance_fields_symbols_1.applicable_accessibilities=private
-dotnet_naming_symbols.private_instance_fields_symbols_1.applicable_kinds=field
-dotnet_naming_symbols.private_instance_fields_symbols_1.resharper_applicable_kinds=field,readonly_field
-dotnet_naming_symbols.private_instance_fields_symbols_1.resharper_required_modifiers=instance
-dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities=private
-dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds=field
-dotnet_naming_symbols.private_static_fields_symbols.required_modifiers=static
-dotnet_naming_symbols.private_static_fields_symbols.resharper_applicable_kinds=field
-dotnet_naming_symbols.private_static_fields_symbols.resharper_required_modifiers=static
-dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities=private
-dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds=field
-dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers=readonly,static
-dotnet_naming_symbols.private_static_readonly_symbols.resharper_applicable_kinds=readonly_field
-dotnet_naming_symbols.private_static_readonly_symbols.resharper_required_modifiers=static
-dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities=*
-dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds=
-dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds=unity_serialised_field
-dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers=instance
-dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities=*
-dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds=
-dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_applicable_kinds=unity_serialised_field
-dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_required_modifiers=instance
-dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
-dotnet_style_parentheses_in_other_binary_operators=always_for_clarity:none
-dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
-dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
-dotnet_style_predefined_type_for_member_access=true:suggestion
-dotnet_style_qualification_for_event=false:suggestion
-dotnet_style_qualification_for_field=false:suggestion
-dotnet_style_qualification_for_method=false:suggestion
-dotnet_style_qualification_for_property=false:suggestion
-dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
+# =====================================================
+# .NET Style – Qualification (kein "this." nötig)
+# =====================================================
-# ReSharper properties
-resharper_autodetect_indent_settings=true
-resharper_cpp_insert_final_newline=true
-resharper_csharp_insert_final_newline=false
-resharper_formatter_off_tag=@formatter:off
-resharper_formatter_on_tag=@formatter:on
-resharper_formatter_tags_enabled=true
-resharper_fsharp_insert_final_newline=false
-resharper_html_insert_final_newline=false
-resharper_resx_insert_final_newline=false
-resharper_shaderlab_insert_final_newline=false
-resharper_t4_insert_final_newline=false
-resharper_use_indent_from_vs=false
-resharper_vb_insert_final_newline=false
-resharper_xmldoc_insert_final_newline=false
-resharper_xml_insert_final_newline=false
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
-# ReSharper inspection severities
-resharper_arrange_redundant_parentheses_highlighting=hint
-resharper_arrange_this_qualifier_highlighting=hint
-resharper_arrange_type_member_modifiers_highlighting=hint
-resharper_arrange_type_modifiers_highlighting=hint
-resharper_built_in_type_reference_style_for_member_access_highlighting=hint
-resharper_built_in_type_reference_style_highlighting=hint
-resharper_razor_assembly_not_resolved_highlighting=warning
-resharper_redundant_base_qualifier_highlighting=warning
-resharper_suggest_var_or_type_built_in_types_highlighting=hint
-resharper_suggest_var_or_type_elsewhere_highlighting=hint
-resharper_suggest_var_or_type_simple_types_highlighting=hint
-resharper_web_config_module_not_resolved_highlighting=warning
-resharper_web_config_type_not_resolved_highlighting=warning
-resharper_web_config_wrong_module_highlighting=warning
-[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.prettierrc.json,.markdownlint.json,.yamllint.json,.stylelintrc,bowerrc,jest.config}]
-indent_style=space
-indent_size=2
+# =====================================================
+# .NET Style – Predefined Types (int statt Int32 etc.)
+# =====================================================
-[{*.yaml,*.yml}]
-indent_style=space
-indent_size=2
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
-[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,c++m,cc,ccm,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,cxxm,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,mxx,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,uxml,vb,xaml,xamlx,xoml,xsd}]
-indent_style=space
-indent_size=4
-tab_width=4
-[*.md]
-trim_trailing_whitespace=false
\ No newline at end of file
+
+# =====================================================
+# .NET Style – Parentheses
+# =====================================================
+
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
+
+
+# =====================================================
+# .NET Style – Accessibility-Modifier erzwingen
+# =====================================================
+
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+
+# ##############################################################
+# #
+# # Naming Conventions (.NET-Standard)
+# #
+# # Private Instance Fields: _camelCase
+# # Private Static Fields: _camelCase
+# # Private Constants: PascalCase
+# # Private Static Readonly: PascalCase
+# #
+# ##############################################################
+
+# === Style: Underscore + camelCase ===
+dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case
+dotnet_naming_style.underscore_camel_case_style.required_prefix = _
+
+# === Style: PascalCase ===
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+
+# === Rule: Private Instance Fields → _camelCase ===
+dotnet_naming_rule.private_instance_fields.severity = warning
+dotnet_naming_rule.private_instance_fields.symbols = private_instance_fields_symbols
+dotnet_naming_rule.private_instance_fields.style = underscore_camel_case_style
+
+dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
+
+
+# === Rule: Private Static Fields → _camelCase ===
+dotnet_naming_rule.private_static_fields.severity = warning
+dotnet_naming_rule.private_static_fields.symbols = private_static_fields_symbols
+dotnet_naming_rule.private_static_fields.style = underscore_camel_case_style
+
+dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
+
+
+# === Rule: Private Constants → PascalCase ===
+dotnet_naming_rule.private_constants.severity = warning
+dotnet_naming_rule.private_constants.symbols = private_constants_symbols
+dotnet_naming_rule.private_constants.style = pascal_case_style
+
+dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
+
+
+# === Rule: Private Static Readonly → PascalCase ===
+dotnet_naming_rule.private_static_readonly.severity = warning
+dotnet_naming_rule.private_static_readonly.symbols = private_static_readonly_symbols
+dotnet_naming_rule.private_static_readonly.style = pascal_case_style
+
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
+
+
+# ##############################################################
+# #
+# # JetBrains Rider / ReSharper Settings
+# #
+# ##############################################################
+
+# === Brace-Style (für ReSharper-spezifische Formatierung) ===
+resharper_csharp_brace_style = next_line
+
+# Kurze Statements ohne Klammern erlaubt (für 1-Zeiler)
+resharper_csharp_braces_for_foreach = not_required
+resharper_csharp_braces_for_for = not_required
+resharper_csharp_braces_for_while = not_required
+
+# === Auto-Detection und Formatter-Tags ===
+resharper_autodetect_indent_settings = true
+resharper_use_indent_from_vs = false
+
+# Erlaubt @formatter:off / @formatter:on Kommentare im Code
+resharper_formatter_off_tag = @formatter:off
+resharper_formatter_on_tag = @formatter:on
+resharper_formatter_tags_enabled = true
+
+
+# =====================================================
+# ReSharper Inspection Severities
+# (Hints = blaue Wellen, Warnings = gelb, Errors = rot)
+# =====================================================
+
+# Style-Suggestions: nur als Hint anzeigen
+resharper_arrange_redundant_parentheses_highlighting = hint
+resharper_arrange_this_qualifier_highlighting = hint
+resharper_arrange_type_member_modifiers_highlighting = hint
+resharper_arrange_type_modifiers_highlighting = hint
+resharper_built_in_type_reference_style_for_member_access_highlighting = hint
+resharper_built_in_type_reference_style_highlighting = hint
+resharper_suggest_var_or_type_built_in_types_highlighting = hint
+resharper_suggest_var_or_type_elsewhere_highlighting = hint
+resharper_suggest_var_or_type_simple_types_highlighting = hint
+
+# Echte Probleme: als Warning
+resharper_redundant_base_qualifier_highlighting = warning
\ No newline at end of file
diff --git a/.env.example b/.env.example
index 9f37f8e..d8b7b7f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,19 +1,50 @@
-# Local development environment template
-#
-# Copy this file to `.env` and adjust paths to your setup,
-# or run: bash scripts/setup-dev-env.sh
-#
-# `.env` is gitignored — never commit your local paths.
-#
-# Activate in shell:
-# set -a; source .env; set +a
-#
-# Or use direnv (recommended):
-# echo 'dotenv .env' > .envrc && direnv allow
+##############################################################
+##
+## .env.example – Hellion Forge / Hellion Media
+##
+## Template für lokale Entwicklungsumgebung.
+## Kopiere diese Datei nach `.env` und passe die Pfade
+## an dein Setup an.
+##
+## ⚠️ `.env` ist gitignored – niemals lokale Pfade committen!
+##
+##############################################################
+##
+## SETUP
+##
+## 1) Manuell:
+## cp .env.example .env
+## # Pfade in .env anpassen
+##
+## 2) Automatisch:
+## bash scripts/setup-dev-env.sh
+##
+## AKTIVIERUNG IN DER SHELL
+##
+## Variante A – einmalig pro Shell:
+## set -a; source .env; set +a
+##
+## Variante B – mit direnv (empfohlen):
+## echo 'dotenv .env' > .envrc
+## direnv allow
+##
+##############################################################
-# Path to Dalamud development DLLs (Dalamud.dll, FFXIVClientStructs.dll,
-# Lumina.dll, Lumina.Excel.dll). Required for building ChatTwo.Tests project.
+
+# =====================================================
+# Build & Development Paths
+# =====================================================
+
+# Pfad zu den Dalamud-Development-DLLs:
+# - Dalamud.dll
+# - FFXIVClientStructs.dll
+# - Lumina.dll
+# - Lumina.Excel.dll
#
-# XIVLauncher Core (Linux): ~/.xlcore/dalamud/Hooks/dev
-# XIVLauncher (Windows): %AppData%\XIVLauncher\addon\Hooks\dev
+# Wird zum Bauen des HellionChat.Tests-Projekts benötigt.
+#
+# Standardpfade je nach Plattform:
+# XIVLauncher Core (Linux): ~/.xlcore/dalamud/Hooks/dev
+# XIVLauncher (Windows): %AppData%\XIVLauncher\addon\Hooks\dev
+# XIVLauncher (macOS): ~/Library/Application Support/XIV on Mac/dalamud/Hooks/dev
DALAMUD_HOME=/path/to/dalamud/dev/dlls
diff --git a/.gitattributes b/.gitattributes
index aa781c8..4e6a098 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,8 +1,178 @@
-# Generated files
-HellionChat/Resources/Language.*.resx linguist-generated=true
+##############################################################
+##
+## .gitattributes – Hellion Forge / Hellion Media
+##
+## Setup: Linux-First Development
+## (Hauptentwicklung auf Linux, Target = Dalamud/Windows)
+## Überarbeitet: Mai 2026
+##
+## Strategie:
+## - Default: Alles LF (Linux-Konvention)
+## - Windows-Batch-Scripts: CRLF (technische Pflicht!)
+## - PowerShell: CRLF (Sicherheit für Windows PS 5.1)
+## - Binärdateien: explizit markiert (gegen Korruption)
+##
+## Hinweis:
+## Moderne Visual-Studio- und MSBuild-Versionen kommen
+## problemlos mit LF in .sln/.csproj klar.
+## Falls jemals Probleme auftauchen: hier umstellen.
+##
+##############################################################
+
+
+# =====================================================
+# Default: Auto-Detect, alles auf LF normalisieren
+# =====================================================
+
* text=auto eol=lf
-*.cs text eol=lf
-*.yml text eol=lf
-*.yaml text eol=lf
-*.md text eol=lf
-*.json text eol=lf
\ No newline at end of file
+
+
+# =====================================================
+# Source Code (LF)
+# =====================================================
+
+*.cs text eol=lf
+*.csx text eol=lf
+*.vb text eol=lf
+*.fs text eol=lf
+*.fsx text eol=lf
+
+
+# =====================================================
+# Configs & Daten (LF)
+# =====================================================
+
+*.json text eol=lf
+*.yml text eol=lf
+*.yaml text eol=lf
+*.xml text eol=lf
+*.md text eol=lf
+*.txt text eol=lf
+*.config text eol=lf
+*.editorconfig text eol=lf
+.gitignore text eol=lf
+.gitattributes text eol=lf
+.env.example text eol=lf
+
+
+# =====================================================
+# Visual Studio / MSBuild Project Files (LF)
+# Linux-first: moderne Tools kommen mit LF zurecht
+# =====================================================
+
+*.sln text eol=lf
+*.csproj text eol=lf
+*.vbproj text eol=lf
+*.fsproj text eol=lf
+*.props text eol=lf
+*.targets text eol=lf
+
+
+# =====================================================
+# Resources & Lokalisierung (LF)
+# =====================================================
+
+# Linguist soll generierte Sprachdateien nicht mitzählen
+HellionChat/Resources/Language.*.resx linguist-generated=true
+
+*.resx text eol=lf
+*.resw text eol=lf
+
+
+# =====================================================
+# Linux/Mac-Scripts (LF – Pflicht)
+# =====================================================
+
+*.sh text eol=lf
+*.bash text eol=lf
+*.zsh text eol=lf
+
+
+# =====================================================
+# >>> AUSNAHMEN <<<
+# Windows-Scripts brauchen ZWINGEND CRLF.
+# Mit LF werden diese auf Windows nicht ausgeführt!
+# =====================================================
+
+# Batch-Scripts (cmd.exe braucht CRLF)
+*.bat text eol=crlf
+*.cmd text eol=crlf
+
+# PowerShell (PS 7+ wäre LF-tolerant,
+# aber Windows PowerShell 5.1 zickt teilweise)
+*.ps1 text eol=crlf
+*.psm1 text eol=crlf
+*.psd1 text eol=crlf
+
+
+# =====================================================
+# Binäre Build-Artefakte
+# =====================================================
+
+*.dll binary
+*.exe binary
+*.pdb binary
+*.so binary
+*.dylib binary
+*.nupkg binary
+*.snupkg binary
+
+
+# =====================================================
+# Bilder (binary)
+# =====================================================
+
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.ico binary
+*.bmp binary
+*.tiff binary
+*.webp binary
+
+# SVG ist eigentlich XML – als Text behandeln
+*.svg text eol=lf
+
+
+# =====================================================
+# Fonts (binary)
+# =====================================================
+
+*.ttf binary
+*.otf binary
+*.woff binary
+*.woff2 binary
+*.eot binary
+
+
+# =====================================================
+# Archive (binary)
+# =====================================================
+
+*.zip binary
+*.7z binary
+*.tar binary
+*.gz binary
+*.rar binary
+
+
+# =====================================================
+# Audio / Video (binary)
+# =====================================================
+
+*.wav binary
+*.mp3 binary
+*.ogg binary
+*.mp4 binary
+
+
+# =====================================================
+# FFXIV / Dalamud spezifische Binär-Formate
+# =====================================================
+
+*.tex binary
+*.pap binary
+*.avfx binary
+*.shpk binary
+*.scd binary
\ No newline at end of file
diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index 8397a02..acedfca 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -11,43 +11,43 @@ name: Build
# Dalamud SDK 15 uses on Linux).
on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
- workflow_dispatch:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
# Minimum permissions for a build-only workflow: read the repo, nothing
# else. Closes the CodeQL "Workflow does not contain permissions" alert
# and matches the principle-of-least-privilege the security guide
# recommends for workflows that don't push or create releases.
permissions:
- contents: read
+ contents: read
jobs:
- build:
- name: Build (Release)
- runs-on: ubuntu-latest
- timeout-minutes: 15
+ build:
+ name: Build (Release)
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
- steps:
- - name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- - name: Setup .NET 10
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
- with:
- dotnet-version: 10.0.x
+ - name: Setup .NET 10
+ uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
+ with:
+ dotnet-version: 10.0.x
- - name: Download Dalamud staging
- run: |
- hooks="$HOME/.xlcore/dalamud/Hooks/dev"
- mkdir -p "$hooks"
- curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
- unzip -oq dalamud.zip -d "$hooks"
+ - name: Download Dalamud staging
+ run: |
+ hooks="$HOME/.xlcore/dalamud/Hooks/dev"
+ mkdir -p "$hooks"
+ curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
+ unzip -oq dalamud.zip -d "$hooks"
- - name: Restore
- run: dotnet restore HellionChat/HellionChat.csproj
+ - name: Restore
+ run: dotnet restore HellionChat/HellionChat.csproj
- - name: Build (Release)
- run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
+ - name: Build (Release)
+ run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
diff --git a/.gitea/workflows/forge-announce.yml b/.gitea/workflows/forge-announce.yml
index 1d646fb..c7e8e6c 100644
--- a/.gitea/workflows/forge-announce.yml
+++ b/.gitea/workflows/forge-announce.yml
@@ -17,234 +17,234 @@ name: Forge Announce
# (issue titles, PR bodies, commit messages, etc.) flows into run-steps.
on:
- push:
- tags:
- - "v*"
- workflow_dispatch:
- inputs:
- tag:
- description: "Existing tag to (re)post, e.g. v1.1.0"
- required: true
- type: string
+ push:
+ tags:
+ - 'v*'
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Existing tag to (re)post, e.g. v1.1.0'
+ required: true
+ type: string
permissions:
- contents: read
+ contents: read
jobs:
- announce:
- name: Post changelog to Hellion Forge
- runs-on: ubuntu-latest
- # The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
- # on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
- # scope for every job by default, no environment: declaration needed.
- timeout-minutes: 5
+ announce:
+ name: Post changelog to Hellion Forge
+ runs-on: ubuntu-latest
+ # The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
+ # on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
+ # scope for every job by default, no environment: declaration needed.
+ timeout-minutes: 5
- steps:
- # On push:tags github.ref points at the tag commit; on workflow_dispatch
- # the user supplies the tag explicitly. Always check out that tag so
- # the yaml + forge-posts file are read from the tagged tree, not main.
- - name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- ref: ${{ github.event.inputs.tag || github.ref }}
+ steps:
+ # On push:tags github.ref points at the tag commit; on workflow_dispatch
+ # the user supplies the tag explicitly. Always check out that tag so
+ # the yaml + forge-posts file are read from the tagged tree, not main.
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: ${{ github.event.inputs.tag || github.ref }}
- # Build embed-payload as a JSON file on disk. PowerShell-Core (pwsh)
- # ships pre-installed on ubuntu-latest so we get the same scripting
- # patterns release.yml uses on windows-latest. Tag is read via env: to
- # treat it as a string variable rather than inline shell text, and
- # validated against the semver regex before any interpolation.
- - name: Build embed payload
- id: build
- shell: pwsh
- env:
- TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
- run: |
- $tag = $env:TAG_NAME
- if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
- throw "V1: Refusing to announce non-semver tag: $tag"
+ # Build embed-payload as a JSON file on disk. PowerShell-Core (pwsh)
+ # ships pre-installed on ubuntu-latest so we get the same scripting
+ # patterns release.yml uses on windows-latest. Tag is read via env: to
+ # treat it as a string variable rather than inline shell text, and
+ # validated against the semver regex before any interpolation.
+ - name: Build embed payload
+ id: build
+ shell: pwsh
+ env:
+ TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
+ run: |
+ $tag = $env:TAG_NAME
+ if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
+ throw "V1: Refusing to announce non-semver tag: $tag"
+ }
+ $version = $tag.Substring(1)
+
+ # ---------- Forge-Post-Datei lesen ----------
+ $forgePath = ".github/forge-posts/$tag.md"
+ if (-not (Test-Path $forgePath)) {
+ throw "V2: Forge-Post-Datei für $tag fehlt unter .github/forge-posts/. Datei vor dem Tag anlegen, dann Tag re-pushen oder workflow_dispatch."
+ }
+ $forgeRaw = Get-Content -Path $forgePath -Raw
+
+ # Frontmatter (--- … ---) am Datei-Anfang
+ if ($forgeRaw -notmatch '(?s)\A---\s*\r?\n(.*?)\r?\n---\s*\r?\n(.*)\z') {
+ throw "V3: Frontmatter (---) fehlt oder ist defekt in $forgePath"
+ }
+ $fmText = $matches[1]
+ $deBody = $matches[2].Trim()
+
+ $subtitle = $null
+ $versionsnatur = $null
+ foreach ($line in ($fmText -split "`r?`n")) {
+ if ($line -match '^subtitle:\s*"?([^"]*)"?\s*$') { $subtitle = $matches[1] }
+ if ($line -match '^versionsnatur:\s*"?([^"]*)"?\s*$') { $versionsnatur = $matches[1] }
+ }
+ if ([string]::IsNullOrWhiteSpace($subtitle)) { throw "V3: Frontmatter-Feld 'subtitle' fehlt in $forgePath" }
+ if ([string]::IsNullOrWhiteSpace($versionsnatur)) { throw "V3: Frontmatter-Feld 'versionsnatur' fehlt in $forgePath" }
+ if ($subtitle.Length -gt 60) { throw "V4: Frontmatter-Feld 'subtitle' überschreitet Limit ($($subtitle.Length) Char, max 60)" }
+ if ($versionsnatur.Length -gt 40) { throw "V4: Frontmatter-Feld 'versionsnatur' überschreitet Limit ($($versionsnatur.Length) Char, max 40)" }
+ if ([string]::IsNullOrWhiteSpace($deBody)) { throw "V3: DE-Body fehlt in $forgePath" }
+
+ # ---------- EN-Block aus HellionChat.yaml ziehen ----------
+ # 1:1 Pattern aus release.yml — gleicher Header-Marker, gleiches
+ # Trailer-Verhalten. Bei Drift die zwei Workflows synchron halten.
+ $yamlPath = "HellionChat/HellionChat.yaml"
+ $raw = Get-Content -Path $yamlPath -Raw
+ $marker = "changelog: |-"
+ $idx = $raw.IndexOf($marker)
+ if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" }
+ $afterMarker = $raw.Substring($idx + $marker.Length)
+ $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
+ if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
+ }) -join "`n"
+
+ $header = "**v$version "
+ $start = $changelogBody.IndexOf($header)
+ if ($start -lt 0) {
+ throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging."
+ }
+ $rest = $changelogBody.Substring($start)
+ $nextHdr = $rest.IndexOf("`n`n**v", 1)
+ $trailer = $rest.IndexOf("`n`n---")
+ if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
+ $enBlock = $rest.Substring(0, $nextHdr).TrimEnd()
+ } elseif ($trailer -ge 0) {
+ $enBlock = $rest.Substring(0, $trailer).TrimEnd()
+ } else {
+ $enBlock = $rest.TrimEnd()
+ }
+
+ # ---------- Embed-Felder + Per-Field-Caps (Discord-Hard-Limits) ----------
+ # Discord enforces per-embed-field limits separately from the
+ # combined-total limit. We split the DE and EN blocks into two
+ # embeds that share the same release URL so Discord stitches
+ # them into one visual card. Hard caps per Discord docs:
+ # description: 4096 per embed
+ # title: 256 per embed
+ # footer.text: 2048 per embed
+ # combined sum across all embeds: 6000
+ $title = "Hellion Chat $version — $subtitle"
+ $deDesc = "**Deutsch**`n`n$deBody"
+ $enDesc = "**English**`n`n$enBlock"
+ $footerText = "Hellion Forge · $versionsnatur"
+ $releaseUrl = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/$tag"
+
+ if ($deDesc.Length -gt 4096) {
+ throw "V6a: DE-Body too long for one embed ($($deDesc.Length) chars, max 4096). Trim .github/forge-posts/$tag.md or post the announcement manually (see forge style §8)."
+ }
+ if ($enDesc.Length -gt 4096) {
+ throw "V6b: EN-Block too long for one embed ($($enDesc.Length) chars, max 4096). Trim the changelog entry in HellionChat/HellionChat.yaml or post manually."
+ }
+ $totalChars = $title.Length + $deDesc.Length + $enDesc.Length + $footerText.Length
+ if ($totalChars -gt 6000) {
+ throw "V6c: Combined embed chars $totalChars exceed Discord's 6000-total limit. Major-Release detected — post manually via Bot/Multi-Embed (see forge style §8)."
+ }
+ Write-Host "Embed-Caps OK: de=$($deDesc.Length)/4096, en=$($enDesc.Length)/4096, total=$totalChars/6000"
+
+ # ---------- Embed-Payload bauen (zwei Embeds, gleiche url) ----------
+ # Sharing the same `url` tells Discord to render both embeds as a
+ # single contiguous card block. The title sits on the first embed,
+ # the footer + timestamp on the last so it reads as one post.
+ $payload = [ordered]@{
+ username = "Forge Herald"
+ avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
+ content = "<@&1500489631555260446>"
+ allowed_mentions = [ordered]@{
+ parse = @()
+ roles = @("1500489631555260446")
+ }
+ embeds = @(
+ [ordered]@{
+ title = $title
+ url = $releaseUrl
+ color = 12730636
+ description = $deDesc
+ },
+ [ordered]@{
+ url = $releaseUrl
+ color = 12730636
+ description = $enDesc
+ footer = [ordered]@{ text = $footerText }
+ timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
}
- $version = $tag.Substring(1)
+ )
+ }
- # ---------- Forge-Post-Datei lesen ----------
- $forgePath = ".github/forge-posts/$tag.md"
- if (-not (Test-Path $forgePath)) {
- throw "V2: Forge-Post-Datei für $tag fehlt unter .github/forge-posts/. Datei vor dem Tag anlegen, dann Tag re-pushen oder workflow_dispatch."
- }
- $forgeRaw = Get-Content -Path $forgePath -Raw
+ $payloadJson = $payload | ConvertTo-Json -Depth 8 -Compress
+ # Ausgabe-Datei ohne trailing newline für sauberes curl --data-binary @-
+ [System.IO.File]::WriteAllText("$PWD/embed-payload.json", $payloadJson, [System.Text.UTF8Encoding]::new($false))
- # Frontmatter (--- … ---) am Datei-Anfang
- if ($forgeRaw -notmatch '(?s)\A---\s*\r?\n(.*?)\r?\n---\s*\r?\n(.*)\z') {
- throw "V3: Frontmatter (---) fehlt oder ist defekt in $forgePath"
- }
- $fmText = $matches[1]
- $deBody = $matches[2].Trim()
+ Write-Host "Payload size: $($payloadJson.Length) chars"
+ Write-Host "Embed title: $title"
+ Write-Host "Embed footer: $footerText"
- $subtitle = $null
- $versionsnatur = $null
- foreach ($line in ($fmText -split "`r?`n")) {
- if ($line -match '^subtitle:\s*"?([^"]*)"?\s*$') { $subtitle = $matches[1] }
- if ($line -match '^versionsnatur:\s*"?([^"]*)"?\s*$') { $versionsnatur = $matches[1] }
- }
- if ([string]::IsNullOrWhiteSpace($subtitle)) { throw "V3: Frontmatter-Feld 'subtitle' fehlt in $forgePath" }
- if ([string]::IsNullOrWhiteSpace($versionsnatur)) { throw "V3: Frontmatter-Feld 'versionsnatur' fehlt in $forgePath" }
- if ($subtitle.Length -gt 60) { throw "V4: Frontmatter-Feld 'subtitle' überschreitet Limit ($($subtitle.Length) Char, max 60)" }
- if ($versionsnatur.Length -gt 40) { throw "V4: Frontmatter-Feld 'versionsnatur' überschreitet Limit ($($versionsnatur.Length) Char, max 40)" }
- if ([string]::IsNullOrWhiteSpace($deBody)) { throw "V3: DE-Body fehlt in $forgePath" }
+ # POST to the Hellion Forge changelog webhook. curl from PowerShell-Core
+ # so we can pipe the payload via stdin (--data-binary @-) and keep
+ # secrets out of process arg lists. One retry on 5xx, hard fail on 4xx.
+ - name: POST to Hellion Forge webhook
+ shell: pwsh
+ env:
+ DISCORD_FORGE_WEBHOOK: ${{ secrets.DISCORD_FORGE_WEBHOOK }}
+ run: |
+ if ([string]::IsNullOrEmpty($env:DISCORD_FORGE_WEBHOOK)) {
+ throw "V7: DISCORD_FORGE_WEBHOOK secret is empty. Check Settings → Environments → Webhook."
+ }
- # ---------- EN-Block aus HellionChat.yaml ziehen ----------
- # 1:1 Pattern aus release.yml — gleicher Header-Marker, gleiches
- # Trailer-Verhalten. Bei Drift die zwei Workflows synchron halten.
- $yamlPath = "HellionChat/HellionChat.yaml"
- $raw = Get-Content -Path $yamlPath -Raw
- $marker = "changelog: |-"
- $idx = $raw.IndexOf($marker)
- if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" }
- $afterMarker = $raw.Substring($idx + $marker.Length)
- $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
- if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
- }) -join "`n"
+ $payloadFile = "$PWD/embed-payload.json"
+ if (-not (Test-Path $payloadFile)) {
+ throw "Embed payload file missing — previous step did not produce embed-payload.json"
+ }
- $header = "**v$version "
- $start = $changelogBody.IndexOf($header)
- if ($start -lt 0) {
- throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging."
- }
- $rest = $changelogBody.Substring($start)
- $nextHdr = $rest.IndexOf("`n`n**v", 1)
- $trailer = $rest.IndexOf("`n`n---")
- if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
- $enBlock = $rest.Substring(0, $nextHdr).TrimEnd()
- } elseif ($trailer -ge 0) {
- $enBlock = $rest.Substring(0, $trailer).TrimEnd()
- } else {
- $enBlock = $rest.TrimEnd()
- }
+ $maxAttempts = 2
+ $attempt = 0
+ while ($attempt -lt $maxAttempts) {
+ $attempt++
+ Write-Host "POST attempt $attempt of $maxAttempts"
+ $tmpResp = "$PWD/.webhook-response"
+ $tmpHeaders = "$PWD/.webhook-headers"
+ # --silent suppresses progress; --show-error prints errors so
+ # the workflow log shows what happened. -w prints HTTP status
+ # to stdout for inspection. -o captures body for diagnosis,
+ # -D captures headers.
+ $rawStatus = Get-Content $payloadFile -Raw |
+ curl --silent --show-error `
+ --header 'Content-Type: application/json' `
+ --data-binary '@-' `
+ -D $tmpHeaders `
+ -o $tmpResp `
+ -w '%{http_code}' `
+ "$env:DISCORD_FORGE_WEBHOOK"
+ $status = [int]$rawStatus
+ Write-Host "HTTP status: $status"
- # ---------- Embed-Felder + Per-Field-Caps (Discord-Hard-Limits) ----------
- # Discord enforces per-embed-field limits separately from the
- # combined-total limit. We split the DE and EN blocks into two
- # embeds that share the same release URL so Discord stitches
- # them into one visual card. Hard caps per Discord docs:
- # description: 4096 per embed
- # title: 256 per embed
- # footer.text: 2048 per embed
- # combined sum across all embeds: 6000
- $title = "Hellion Chat $version — $subtitle"
- $deDesc = "**Deutsch**`n`n$deBody"
- $enDesc = "**English**`n`n$enBlock"
- $footerText = "Hellion Forge · $versionsnatur"
- $releaseUrl = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/$tag"
+ if ($status -ge 200 -and $status -lt 300) {
+ Write-Host "Forge announce POST succeeded."
+ exit 0
+ }
- if ($deDesc.Length -gt 4096) {
- throw "V6a: DE-Body too long for one embed ($($deDesc.Length) chars, max 4096). Trim .github/forge-posts/$tag.md or post the announcement manually (see forge style §8)."
- }
- if ($enDesc.Length -gt 4096) {
- throw "V6b: EN-Block too long for one embed ($($enDesc.Length) chars, max 4096). Trim the changelog entry in HellionChat/HellionChat.yaml or post manually."
- }
- $totalChars = $title.Length + $deDesc.Length + $enDesc.Length + $footerText.Length
- if ($totalChars -gt 6000) {
- throw "V6c: Combined embed chars $totalChars exceed Discord's 6000-total limit. Major-Release detected — post manually via Bot/Multi-Embed (see forge style §8)."
- }
- Write-Host "Embed-Caps OK: de=$($deDesc.Length)/4096, en=$($enDesc.Length)/4096, total=$totalChars/6000"
+ $bodySnippet = ""
+ if (Test-Path $tmpResp) {
+ $bodySnippet = (Get-Content $tmpResp -Raw -ErrorAction SilentlyContinue)
+ if ($bodySnippet.Length -gt 500) { $bodySnippet = $bodySnippet.Substring(0, 500) + " …" }
+ }
- # ---------- Embed-Payload bauen (zwei Embeds, gleiche url) ----------
- # Sharing the same `url` tells Discord to render both embeds as a
- # single contiguous card block. The title sits on the first embed,
- # the footer + timestamp on the last so it reads as one post.
- $payload = [ordered]@{
- username = "Forge Herald"
- avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
- content = "<@&1500489631555260446>"
- allowed_mentions = [ordered]@{
- parse = @()
- roles = @("1500489631555260446")
- }
- embeds = @(
- [ordered]@{
- title = $title
- url = $releaseUrl
- color = 12730636
- description = $deDesc
- },
- [ordered]@{
- url = $releaseUrl
- color = 12730636
- description = $enDesc
- footer = [ordered]@{ text = $footerText }
- timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
- }
- )
- }
+ if ($status -ge 400 -and $status -lt 500) {
+ # E2: 4xx is permanent — webhook revoked, channel deleted,
+ # payload malformed. No retry.
+ throw "E2: Discord-Webhook returned permanent $status. Body: $bodySnippet"
+ }
- $payloadJson = $payload | ConvertTo-Json -Depth 8 -Compress
- # Ausgabe-Datei ohne trailing newline für sauberes curl --data-binary @-
- [System.IO.File]::WriteAllText("$PWD/embed-payload.json", $payloadJson, [System.Text.UTF8Encoding]::new($false))
-
- Write-Host "Payload size: $($payloadJson.Length) chars"
- Write-Host "Embed title: $title"
- Write-Host "Embed footer: $footerText"
-
- # POST to the Hellion Forge changelog webhook. curl from PowerShell-Core
- # so we can pipe the payload via stdin (--data-binary @-) and keep
- # secrets out of process arg lists. One retry on 5xx, hard fail on 4xx.
- - name: POST to Hellion Forge webhook
- shell: pwsh
- env:
- DISCORD_FORGE_WEBHOOK: ${{ secrets.DISCORD_FORGE_WEBHOOK }}
- run: |
- if ([string]::IsNullOrEmpty($env:DISCORD_FORGE_WEBHOOK)) {
- throw "V7: DISCORD_FORGE_WEBHOOK secret is empty. Check Settings → Environments → Webhook."
- }
-
- $payloadFile = "$PWD/embed-payload.json"
- if (-not (Test-Path $payloadFile)) {
- throw "Embed payload file missing — previous step did not produce embed-payload.json"
- }
-
- $maxAttempts = 2
- $attempt = 0
- while ($attempt -lt $maxAttempts) {
- $attempt++
- Write-Host "POST attempt $attempt of $maxAttempts"
- $tmpResp = "$PWD/.webhook-response"
- $tmpHeaders = "$PWD/.webhook-headers"
- # --silent suppresses progress; --show-error prints errors so
- # the workflow log shows what happened. -w prints HTTP status
- # to stdout for inspection. -o captures body for diagnosis,
- # -D captures headers.
- $rawStatus = Get-Content $payloadFile -Raw |
- curl --silent --show-error `
- --header 'Content-Type: application/json' `
- --data-binary '@-' `
- -D $tmpHeaders `
- -o $tmpResp `
- -w '%{http_code}' `
- "$env:DISCORD_FORGE_WEBHOOK"
- $status = [int]$rawStatus
- Write-Host "HTTP status: $status"
-
- if ($status -ge 200 -and $status -lt 300) {
- Write-Host "Forge announce POST succeeded."
- exit 0
- }
-
- $bodySnippet = ""
- if (Test-Path $tmpResp) {
- $bodySnippet = (Get-Content $tmpResp -Raw -ErrorAction SilentlyContinue)
- if ($bodySnippet.Length -gt 500) { $bodySnippet = $bodySnippet.Substring(0, 500) + " …" }
- }
-
- if ($status -ge 400 -and $status -lt 500) {
- # E2: 4xx is permanent — webhook revoked, channel deleted,
- # payload malformed. No retry.
- throw "E2: Discord-Webhook returned permanent $status. Body: $bodySnippet"
- }
-
- # E1: 5xx (or transport-level fail with status 0) — wait + retry once
- if ($attempt -lt $maxAttempts) {
- Write-Host "Transient $status — sleeping 30s before retry."
- Start-Sleep -Seconds 30
- } else {
- throw "E1: Discord-Webhook returned transient $status after $maxAttempts attempts. Body: $bodySnippet"
- }
- }
+ # E1: 5xx (or transport-level fail with status 0) — wait + retry once
+ if ($attempt -lt $maxAttempts) {
+ Write-Host "Transient $status — sleeping 30s before retry."
+ Start-Sleep -Seconds 30
+ } else {
+ throw "E1: Discord-Webhook returned transient $status after $maxAttempts attempts. Body: $bodySnippet"
+ }
+ }
diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 623ac5a..7920761 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -17,167 +17,167 @@ name: Release
# Linux when the Dalamud staging assemblies sit under $(HOME)/.xlcore/...
on:
- push:
- tags:
- - "v*"
- # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
- # tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
- # ref step below hard-fails if a non-tag ref is selected, because the
- # release-action reads GITHUB_REF directly and rejects anything that
- # does not start with refs/tags/.
- workflow_dispatch:
+ push:
+ tags:
+ - 'v*'
+ # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
+ # tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
+ # ref step below hard-fails if a non-tag ref is selected, because the
+ # release-action reads GITHUB_REF directly and rejects anything that
+ # does not start with refs/tags/.
+ workflow_dispatch:
permissions:
- contents: write
+ contents: write
jobs:
- release:
- name: Build and attach release ZIP
- runs-on: ubuntu-latest
- timeout-minutes: 20
+ release:
+ name: Build and attach release ZIP
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
- steps:
- # release-action@main reads GITHUB_REF directly (its action.yml
- # does not declare a tag_name input). Validate up-front so manual
- # dispatches from a branch ref fail loud here instead of burning
- # a full build before the final step errors out with "ref X is
- # not a tag".
- - name: Validate tag ref
- run: |
- if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
- echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}"
- echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown."
- exit 1
- fi
+ steps:
+ # release-action@main reads GITHUB_REF directly (its action.yml
+ # does not declare a tag_name input). Validate up-front so manual
+ # dispatches from a branch ref fail loud here instead of burning
+ # a full build before the final step errors out with "ref X is
+ # not a tag".
+ - name: Validate tag ref
+ run: |
+ if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
+ echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}"
+ echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown."
+ exit 1
+ fi
- - name: Checkout
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- - name: Setup .NET 10
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
- with:
- dotnet-version: 10.0.x
+ - name: Setup .NET 10
+ uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
+ with:
+ dotnet-version: 10.0.x
- - name: Download Dalamud staging
- run: |
- hooks="$HOME/.xlcore/dalamud/Hooks/dev"
- mkdir -p "$hooks"
- curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
- unzip -oq dalamud.zip -d "$hooks"
+ - name: Download Dalamud staging
+ run: |
+ hooks="$HOME/.xlcore/dalamud/Hooks/dev"
+ mkdir -p "$hooks"
+ curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
+ unzip -oq dalamud.zip -d "$hooks"
- - name: Build (Release)
- run: dotnet build HellionChat/HellionChat.csproj --configuration Release
+ - name: Build (Release)
+ run: dotnet build HellionChat/HellionChat.csproj --configuration Release
- - name: Locate latest.zip
- id: locate
- run: |
- zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
- if [ -z "$zip" ]; then
- echo "latest.zip not found under HellionChat/bin/Release" >&2
- exit 1
- fi
- echo "Found: $zip"
- echo "path=$zip" >> "$GITHUB_OUTPUT"
+ - name: Locate latest.zip
+ id: locate
+ run: |
+ zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
+ if [ -z "$zip" ]; then
+ echo "latest.zip not found under HellionChat/bin/Release" >&2
+ exit 1
+ fi
+ echo "Found: $zip"
+ echo "path=$zip" >> "$GITHUB_OUTPUT"
- # Build a release body from the matching changelog block in
- # HellionChat.yaml plus a static install / docs footer. Fails the
- # workflow if no block exists for the tagged version, which is the
- # automated counterpart to the "yaml + repo.json + release body
- # kept in sync" rule.
- #
- # GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
- # tag value is treated as a PowerShell variable, not as inline shell
- # text. The strict regex below rejects anything that is not a clean
- # semver tag before it is used to build a string.
- - name: Generate release body
- shell: pwsh
- env:
- # github.ref_name is the tag because Validate tag ref above
- # already enforced refs/tags/v*. Read via env: so the value
- # is a PowerShell variable, not inline shell text, and gets
- # re-validated against the semver regex below.
- TAG_NAME: ${{ github.ref_name }}
- run: |
- $tag = $env:TAG_NAME
- if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
- throw "Refusing to generate release body for non-semver tag: $tag"
- }
- $version = $tag.Substring(1)
+ # Build a release body from the matching changelog block in
+ # HellionChat.yaml plus a static install / docs footer. Fails the
+ # workflow if no block exists for the tagged version, which is the
+ # automated counterpart to the "yaml + repo.json + release body
+ # kept in sync" rule.
+ #
+ # GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
+ # tag value is treated as a PowerShell variable, not as inline shell
+ # text. The strict regex below rejects anything that is not a clean
+ # semver tag before it is used to build a string.
+ - name: Generate release body
+ shell: pwsh
+ env:
+ # github.ref_name is the tag because Validate tag ref above
+ # already enforced refs/tags/v*. Read via env: so the value
+ # is a PowerShell variable, not inline shell text, and gets
+ # re-validated against the semver regex below.
+ TAG_NAME: ${{ github.ref_name }}
+ run: |
+ $tag = $env:TAG_NAME
+ if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
+ throw "Refusing to generate release body for non-semver tag: $tag"
+ }
+ $version = $tag.Substring(1)
- $yamlPath = "HellionChat/HellionChat.yaml"
- $raw = Get-Content -Path $yamlPath -Raw
+ $yamlPath = "HellionChat/HellionChat.yaml"
+ $raw = Get-Content -Path $yamlPath -Raw
- $marker = "changelog: |-"
- $idx = $raw.IndexOf($marker)
- if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
+ $marker = "changelog: |-"
+ $idx = $raw.IndexOf($marker)
+ if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
- # changelog: is the last top-level key in the manifest, so
- # everything after the marker is the literal block. Strip the
- # 4-space yaml indent (prettier convention) from each line.
- $afterMarker = $raw.Substring($idx + $marker.Length)
- $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
- if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
- }) -join "`n"
+ # changelog: is the last top-level key in the manifest, so
+ # everything after the marker is the literal block. Strip the
+ # 4-space yaml indent (prettier convention) from each line.
+ $afterMarker = $raw.Substring($idx + $marker.Length)
+ $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
+ if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
+ }) -join "`n"
- # Subblock convention: "**vX.Y.Z — ()**"
- # matches verify-changelog-sync.sh and slim-rule grep.
- $header = "**v$version "
- $start = $changelogBody.IndexOf($header)
- if ($start -lt 0) {
- throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
- }
+ # Subblock convention: "**vX.Y.Z — ()**"
+ # matches verify-changelog-sync.sh and slim-rule grep.
+ $header = "**v$version "
+ $start = $changelogBody.IndexOf($header)
+ if ($start -lt 0) {
+ throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
+ }
- $rest = $changelogBody.Substring($start)
- $nextHdr = $rest.IndexOf("`n`n**v", 1)
- $trailer = $rest.IndexOf("`n`n---")
+ $rest = $changelogBody.Substring($start)
+ $nextHdr = $rest.IndexOf("`n`n**v", 1)
+ $trailer = $rest.IndexOf("`n`n---")
- if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
- $currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
- } elseif ($trailer -ge 0) {
- $currentBlock = $rest.Substring(0, $trailer).TrimEnd()
- } else {
- $currentBlock = $rest.TrimEnd()
- }
+ if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
+ $currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
+ } elseif ($trailer -ge 0) {
+ $currentBlock = $rest.Substring(0, $trailer).TrimEnd()
+ } else {
+ $currentBlock = $rest.TrimEnd()
+ }
- # Static install / docs / licence footer is maintained as a
- # separate file so the workflow YAML stays clean (no embedded
- # heredoc that would have to be indented under the run-block).
- $footerPath = ".github/release-footer.md"
- if (-not (Test-Path $footerPath)) {
- throw "Release footer template not found: $footerPath"
- }
- $footer = Get-Content -Path $footerPath -Raw
+ # Static install / docs / licence footer is maintained as a
+ # separate file so the workflow YAML stays clean (no embedded
+ # heredoc that would have to be indented under the run-block).
+ $footerPath = ".github/release-footer.md"
+ if (-not (Test-Path $footerPath)) {
+ throw "Release footer template not found: $footerPath"
+ }
+ $footer = Get-Content -Path $footerPath -Raw
- $body = $currentBlock + "`n" + $footer
- $body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
+ $body = $currentBlock + "`n" + $footer
+ $body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
- Write-Host "Generated release body for $tag :"
- Write-Host "----------------------------------------"
- Write-Host $body
- Write-Host "----------------------------------------"
+ Write-Host "Generated release body for $tag :"
+ Write-Host "----------------------------------------"
+ Write-Host $body
+ Write-Host "----------------------------------------"
- # release-action@main only declares files/title/body/pre_release/
- # draft/api_key/insecure as inputs (see its action.yml). It silently
- # ignores anything else, including body_path and tag_name. The tag
- # itself comes from GITHUB_REF, the body must be passed inline via
- # body:, so we re-emit release-body.md as a step output first.
- - name: Expose release body for release-action
- id: body
- shell: bash
- run: |
- {
- echo 'content<> "$GITHUB_OUTPUT"
+ # release-action@main only declares files/title/body/pre_release/
+ # draft/api_key/insecure as inputs (see its action.yml). It silently
+ # ignores anything else, including body_path and tag_name. The tag
+ # itself comes from GITHUB_REF, the body must be passed inline via
+ # body:, so we re-emit release-body.md as a step output first.
+ - name: Expose release body for release-action
+ id: body
+ shell: bash
+ run: |
+ {
+ echo 'content<> "$GITHUB_OUTPUT"
- # Gitea-native release action. Creates the release if the tag has no
- # release yet, or updates the existing one with latest.zip attached
- # and the generated body. The auto-injected GITHUB_TOKEN on Gitea
- # Actions has Gitea-API scope and is sufficient for release write.
- - name: Attach to Gitea release
- uses: https://gitea.com/actions/release-action@main
- with:
- files: ${{ steps.locate.outputs.path }}
- body: ${{ steps.body.outputs.content }}
- api_key: ${{ secrets.GITHUB_TOKEN }}
+ # Gitea-native release action. Creates the release if the tag has no
+ # release yet, or updates the existing one with latest.zip attached
+ # and the generated body. The auto-injected GITHUB_TOKEN on Gitea
+ # Actions has Gitea-API scope and is sufficient for release write.
+ - name: Attach to Gitea release
+ uses: https://gitea.com/actions/release-action@main
+ with:
+ files: ${{ steps.locate.outputs.path }}
+ body: ${{ steps.body.outputs.content }}
+ api_key: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml
index 838eaf7..0f484d0 100644
--- a/.gitea/workflows/security.yml
+++ b/.gitea/workflows/security.yml
@@ -1,20 +1,20 @@
name: Security
on:
- push:
- branches: [main, master]
- pull_request:
- schedule:
- - cron: "0 6 * * 1"
- workflow_dispatch:
+ push:
+ branches: [main, master]
+ pull_request:
+ schedule:
+ - cron: '0 6 * * 1'
+ workflow_dispatch:
jobs:
- scan:
- uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
- with:
- # MessageStore.cs uses string-interpolation in CommandText for table
- # names and clause-joins that come from internal code constants, not
- # user input. Values are bound via SqlParameter, the SQL surface is
- # local-only inside a Dalamud plugin. Semgrep matches the pattern
- # without dataflow, so it flags those eight call sites; CodeQL
- # would not. Suppressed for this repo only.
- semgrep-exclude-rules: "csharp.lang.security.sqli.csharp-sqli.csharp-sqli"
+ scan:
+ uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
+ with:
+ # MessageStore.cs uses string-interpolation in CommandText for table
+ # names and clause-joins that come from internal code constants, not
+ # user input. Values are bound via SqlParameter, the SQL surface is
+ # local-only inside a Dalamud plugin. Semgrep matches the pattern
+ # without dataflow, so it flags those eight call sites; CodeQL
+ # would not. Suppressed for this repo only.
+ semgrep-exclude-rules: 'csharp.lang.security.sqli.csharp-sqli.csharp-sqli'
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 5c9fc4a..0b24ac5 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,73 +1,73 @@
name: Bug report
description: Something in HellionChat is broken or behaves wrong
labels:
- - bug
+ - bug
body:
- - type: markdown
- attributes:
- value: |
- Thanks for reporting. Please fill in the fields below so I can
- reproduce the issue. If this is a security issue, stop here and
- report it privately to [kontakt@hellion-media.de](mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D)
- instead.
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for reporting. Please fill in the fields below so I can
+ reproduce the issue. If this is a security issue, stop here and
+ report it privately to [kontakt@hellion-media.de](mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D)
+ instead.
- - type: input
- id: version
- attributes:
- label: HellionChat version
- description: From Settings → Information → Version
- placeholder: "0.5.4"
- validations:
+ - type: input
+ id: version
+ attributes:
+ label: HellionChat version
+ description: From Settings → Information → Version
+ placeholder: '0.5.4'
+ validations:
+ required: true
+
+ - type: dropdown
+ id: platform
+ attributes:
+ label: Platform
+ options:
+ - Windows (XIVLauncher)
+ - Linux (XIVLauncher Core)
+ - macOS (XIVLauncher Core / wine)
+ - Other
+ validations:
+ required: true
+
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened
+ description: Plain description, no log dumps yet
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected
+ attributes:
+ label: What you expected
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: How to reproduce
+ description: Step-by-step from "open settings" or "log in" through to the broken behaviour
+ validations:
+ required: true
+
+ - type: textarea
+ id: log
+ attributes:
+ label: Relevant /xllog excerpt
+ description: Filter for "HellionChat" if the log is huge
+ render: text
+
+ - type: checkboxes
+ id: confirm
+ attributes:
+ label: Pre-flight
+ options:
+ - label: I am running the latest version of HellionChat
required: true
-
- - type: dropdown
- id: platform
- attributes:
- label: Platform
- options:
- - Windows (XIVLauncher)
- - Linux (XIVLauncher Core)
- - macOS (XIVLauncher Core / wine)
- - Other
- validations:
+ - label: I have searched existing issues for duplicates
required: true
-
- - type: textarea
- id: what-happened
- attributes:
- label: What happened
- description: Plain description, no log dumps yet
- validations:
- required: true
-
- - type: textarea
- id: expected
- attributes:
- label: What you expected
- validations:
- required: true
-
- - type: textarea
- id: steps
- attributes:
- label: How to reproduce
- description: Step-by-step from "open settings" or "log in" through to the broken behaviour
- validations:
- required: true
-
- - type: textarea
- id: log
- attributes:
- label: Relevant /xllog excerpt
- description: Filter for "HellionChat" if the log is huge
- render: text
-
- - type: checkboxes
- id: confirm
- attributes:
- label: Pre-flight
- options:
- - label: I am running the latest version of HellionChat
- required: true
- - label: I have searched existing issues for duplicates
- required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 132da67..656eddd 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,15 +1,15 @@
blank_issues_enabled: false
contact_links:
- - name: Security vulnerability
- url: mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
- about: Do not open a public issue for security problems. Report by e-mail instead.
+ - name: Security vulnerability
+ url: mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
+ about: Do not open a public issue for security problems. Report by e-mail instead.
- - name: Upstream Chat 2 issue
- url: https://github.com/Infiziert90/ChatTwo/issues
- about:
- If the issue exists in upstream Chat 2 too, please report it there so the original maintainers see it as well.
+ - name: Upstream Chat 2 issue
+ url: https://github.com/Infiziert90/ChatTwo/issues
+ about:
+ If the issue exists in upstream Chat 2 too, please report it there so the original maintainers see it as well.
- - name: Discord
- url: https://discord.com/users/j.j_kazama
- about: Quick questions, casual feedback. Bug reports still go through the issue tracker for tracking.
+ - name: Discord
+ url: https://discord.com/users/j.j_kazama
+ about: Quick questions, casual feedback. Bug reports still go through the issue tracker for tracking.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index fe74fd4..e9fd8b8 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,57 +1,55 @@
name: Feature request
description: Suggest a feature or enhancement for HellionChat
labels:
- - enhancement
+ - enhancement
body:
- - type: markdown
- attributes:
- value: |
- Thanks for the suggestion. HellionChat focuses on privacy by
- default and a small, well-scoped feature set. Suggestions that
- align with that scope are easier to accept than ones that pull
- the plugin toward "do everything".
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for the suggestion. HellionChat focuses on privacy by
+ default and a small, well-scoped feature set. Suggestions that
+ align with that scope are easier to accept than ones that pull
+ the plugin toward "do everything".
- - type: textarea
- id: problem
- attributes:
- label: What problem are you trying to solve
- description: The user-side problem, not the proposed solution yet
- validations:
+ - type: textarea
+ id: problem
+ attributes:
+ label: What problem are you trying to solve
+ description: The user-side problem, not the proposed solution yet
+ validations:
+ required: true
+
+ - type: textarea
+ id: solution
+ attributes:
+ label: What you would like HellionChat to do
+ validations:
+ required: true
+
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives you have considered
+ description: Other plugins, manual workarounds, settings combinations
+
+ - type: dropdown
+ id: scope
+ attributes:
+ label: Scope estimate from your side
+ options:
+ - 'Small (one tab, one toggle, one filter)'
+ - 'Medium (a settings section, persistent state, one new file)'
+ - 'Large (architectural, touches the message pipeline or the database)'
+ - "I don't know"
+ validations:
+ required: true
+
+ - type: checkboxes
+ id: confirm
+ attributes:
+ label: Pre-flight
+ options:
+ - label: I have searched existing issues for similar requests
required: true
-
- - type: textarea
- id: solution
- attributes:
- label: What you would like HellionChat to do
- validations:
+ - label: I understand HellionChat is a privacy-focused fork and not a feature parity tool with upstream Chat 2
required: true
-
- - type: textarea
- id: alternatives
- attributes:
- label: Alternatives you have considered
- description: Other plugins, manual workarounds, settings combinations
-
- - type: dropdown
- id: scope
- attributes:
- label: Scope estimate from your side
- options:
- - "Small (one tab, one toggle, one filter)"
- - "Medium (a settings section, persistent state, one new file)"
- - "Large (architectural, touches the message pipeline or the database)"
- - "I don't know"
- validations:
- required: true
-
- - type: checkboxes
- id: confirm
- attributes:
- label: Pre-flight
- options:
- - label: I have searched existing issues for similar requests
- required: true
- - label:
- I understand HellionChat is a privacy-focused fork and not a feature parity tool with upstream Chat
- 2
- required: true
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 52c58ab..1580a01 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -18,7 +18,8 @@ mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds behaviour)
-- [ ] Breaking change (config migration, removed feature, or behaviour change that user-visible defaults rely on)
+- [ ] Breaking change (config migration, removed feature, or behaviour change that user-visible
+ defaults rely on)
- [ ] Documentation only
- [ ] Translation update
- [ ] Build, CI or tooling change
@@ -55,10 +56,11 @@ new commands, new translations, removed behaviour. If none, write
## Checklist
-- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md).
+- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and
+ [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md).
- [ ] My change matches the existing code style (`.editorconfig`).
-- [ ] I added or updated tests where the existing test infrastructure made that practical, or I have explained why tests
- are not applicable.
+- [ ] I added or updated tests where the existing test infrastructure made that practical, or I have
+ explained why tests are not applicable.
- [ ] I updated the README, in-plugin strings or documentation if my change is user-visible.
- [ ] I did not include any AI-generated code without disclosing it in the PR description (see
[AI_DISCLOSURE.md](../docs/AI_DISCLOSURE.md)).
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 2fff09b..10e7def 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,42 +1,42 @@
version: 2
updates:
- # NuGet package updates for the plugin project. Weekly cadence keeps the
- # noise down while still catching transitive security advisories within
- # a few days of disclosure.
- - package-ecosystem: nuget
- directory: /HellionChat
- schedule:
- interval: weekly
- day: monday
- time: "07:00"
- timezone: Europe/Berlin
- open-pull-requests-limit: 5
- labels:
- - dependencies
- - nuget
- commit-message:
- prefix: "chore(deps)"
- groups:
- patches:
- update-types:
- - patch
- minor:
- update-types:
- - minor
+ # NuGet package updates for the plugin project. Weekly cadence keeps the
+ # noise down while still catching transitive security advisories within
+ # a few days of disclosure.
+ - package-ecosystem: nuget
+ directory: /HellionChat
+ schedule:
+ interval: weekly
+ day: monday
+ time: '07:00'
+ timezone: Europe/Berlin
+ open-pull-requests-limit: 5
+ labels:
+ - dependencies
+ - nuget
+ commit-message:
+ prefix: 'chore(deps)'
+ groups:
+ patches:
+ update-types:
+ - patch
+ minor:
+ update-types:
+ - minor
- # GitHub Actions versions in .github/workflows. Lower cadence because
- # Action releases ship less frequently and are usually safe to defer
- # for a month.
- - package-ecosystem: github-actions
- directory: /
- schedule:
- interval: monthly
- time: "07:00"
- timezone: Europe/Berlin
- open-pull-requests-limit: 3
- labels:
- - dependencies
- - github-actions
- commit-message:
- prefix: "chore(actions)"
+ # GitHub Actions versions in .github/workflows. Lower cadence because
+ # Action releases ship less frequently and are usually safe to defer
+ # for a month.
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: monthly
+ time: '07:00'
+ timezone: Europe/Berlin
+ open-pull-requests-limit: 3
+ labels:
+ - dependencies
+ - github-actions
+ commit-message:
+ prefix: 'chore(actions)'
diff --git a/.github/forge-posts/v1.1.0.md b/.github/forge-posts/v1.1.0.md
index 888aca7..bbc066e 100644
--- a/.github/forge-posts/v1.1.0.md
+++ b/.github/forge-posts/v1.1.0.md
@@ -3,15 +3,16 @@ subtitle: "Theme Foundation"
versionsnatur: "Major-UI-Cycle"
---
-- Theme-Engine mit fünf Built-In-Themes: Hellion Arctic (Default), Chat 2 Klassik, Event Horizon, Moonlit Bloom, Mint
- Grove
-- Settings öffnet jetzt eine Card-Grid-Übersicht — Klick auf eine Card führt in den Detail-View, Breadcrumb und ESC
- zurück zur Übersicht
+- Theme-Engine mit fünf Built-In-Themes: Hellion Arctic (Default), Chat 2 Klassik, Event Horizon,
+ Moonlit Bloom, Mint Grove
+- Settings öffnet jetzt eine Card-Grid-Übersicht — Klick auf eine Card führt in den Detail-View,
+ Breadcrumb und ESC zurück zur Übersicht
- Themes-Tab mit Mini-Mockup pro Theme, Live-Switch beim Klick
-- Eigene Themes als JSON in `pluginConfigs/HellionChat/themes/` — Beispiel-Vorlage wird beim ersten Start automatisch
- abgelegt
-- Optional pro Theme eigene Chat-Channel-Farben mit Übernehmen/Behalten-Banner — niemals automatisch überschrieben
+- Eigene Themes als JSON in `pluginConfigs/HellionChat/themes/` — Beispiel-Vorlage wird beim ersten
+ Start automatisch abgelegt
+- Optional pro Theme eigene Chat-Channel-Farben mit Übernehmen/Behalten-Banner — niemals automatisch
+ überschrieben
- Plugin-Icon zum Hellion-Forge-Hammer gewechselt
-- Migration v13 → v14: alle User landen auf Hellion Arctic. Wer den Upstream-Look will, wählt Chat 2 Klassik in Settings
- → Themes
+- Migration v13 → v14: alle User landen auf Hellion Arctic. Wer den Upstream-Look will, wählt Chat 2
+ Klassik in Settings → Themes
- Anleitung zum Schreiben eigener Themes: `docs/THEME-AUTHORING.md`
diff --git a/.github/forge-posts/v1.2.0.md b/.github/forge-posts/v1.2.0.md
index 5275957..a6d87d0 100644
--- a/.github/forge-posts/v1.2.0.md
+++ b/.github/forge-posts/v1.2.0.md
@@ -3,22 +3,22 @@ subtitle: "Layout Refresh"
versionsnatur: "Major-UI-Cycle"
---
-- Sidebar im neuen Look: fix 44 px breit, nur Icons, Tab-Name als Tooltip beim Hover, vertikale Akzent-Pill markiert den
- aktiven Tab
+- Sidebar im neuen Look: fix 44 px breit, nur Icons, Tab-Name als Tooltip beim Hover, vertikale
+ Akzent-Pill markiert den aktiven Tab
- Top-Tabs bekommen eine Akzent-Underline statt Background-Fill am aktiven Tab
- Pro Tab eigenes Icon wählbar in Einstellungen → Tabs (FontAwesome-Pool)
- Auto-Tell-Tabs sind jetzt visuell unterscheidbar: jeder Tell-Partner bekommt ein eigenes Icon
- (envelope/star/heart/bell/bookmark/flag/fire) plus eigene Farbe aus 12-Farb-Palette — 84 Kombinationen, gleicher
- Partner ergibt konsistent dieselbe
-- Pulsierender roter Dot oben rechts am Sidebar-Icon zeigt ungelesene Nachrichten an. Sanft, 2-Sekunden-Cycle,
- deaktivierbar über `Configuration.ReduceMotion` (UI-Toggle in v1.3.0)
-- Bottom-Status-Bar (22 px) mit fünf Live-Slots: aktiver Channel + Color-Dot, Privacy-Badge, Tab/Message-Counter,
- Auto-Tell-Counter, Plugin-Version. Update 1×/Sek
-- Card-Rows als Default-Message-Render: Sender-Header in Channel-Farbe, Body neue Zeile, dezenter Trenner.
- `Compact Density`-Toggle in Aussehen schaltet zurück auf den Einzeiler
-- Bug-Fix: Settings speichern löscht den Chat-Verlauf nicht mehr. Refilter läuft jetzt nur wenn Filter-relevante
- Settings geändert wurden — Cosmetic-Änderungen lassen den Chat unverändert. Persistente und Auto-Tell-Tabs überleben
- beide
+ (envelope/star/heart/bell/bookmark/flag/fire) plus eigene Farbe aus 12-Farb-Palette — 84
+ Kombinationen, gleicher Partner ergibt konsistent dieselbe
+- Pulsierender roter Dot oben rechts am Sidebar-Icon zeigt ungelesene Nachrichten an. Sanft,
+ 2-Sekunden-Cycle, deaktivierbar über `Configuration.ReduceMotion` (UI-Toggle in v1.3.0)
+- Bottom-Status-Bar (22 px) mit fünf Live-Slots: aktiver Channel + Color-Dot, Privacy-Badge,
+ Tab/Message-Counter, Auto-Tell-Counter, Plugin-Version. Update 1×/Sek
+- Card-Rows als Default-Message-Render: Sender-Header in Channel-Farbe, Body neue Zeile, dezenter
+ Trenner. `Compact Density`-Toggle in Aussehen schaltet zurück auf den Einzeiler
+- Bug-Fix: Settings speichern löscht den Chat-Verlauf nicht mehr. Refilter läuft jetzt nur wenn
+ Filter-relevante Settings geändert wurden — Cosmetic-Änderungen lassen den Chat unverändert.
+ Persistente und Auto-Tell-Tabs überleben beide
- Bug-Fix: Hellion-Schrift (Exo 2) blockt die Schriftgröße nicht mehr — 4K-User können hochskalieren
- Migration v14 → v15: alte Theme-Felder entfernt, alle anderen Settings bleiben
diff --git a/.github/forge-posts/v1.2.1.md b/.github/forge-posts/v1.2.1.md
index e98f68f..5528f6b 100644
--- a/.github/forge-posts/v1.2.1.md
+++ b/.github/forge-posts/v1.2.1.md
@@ -3,27 +3,31 @@ subtitle: "Settings Cleanup"
versionsnatur: "UX-Polish-Cycle"
---
-- Settings-Übersicht thematisch re-sortiert: zusammenhängende Optionen wohnen jetzt zusammen, jede Card hat einen kurzen
- Untertitel — kein Raten mehr wo eine Setting steckt
-- Drei neue Cards: **Theme & Layout** (Theme-Picker, Fenster-Style, Zeitstempel-Style), **Schriften & Farben**
- (Schriftart, Schriftgröße, Chat-Farben pro Channel), **Daten-Verwaltung** (Aufbewahrung, Cleanup, Export, DB-Viewer,
- Advanced-Tools — vorher zwischen Datenschutz und Datenbank verteilt)
+- Settings-Übersicht thematisch re-sortiert: zusammenhängende Optionen wohnen jetzt zusammen, jede
+ Card hat einen kurzen Untertitel — kein Raten mehr wo eine Setting steckt
+- Drei neue Cards: **Theme & Layout** (Theme-Picker, Fenster-Style, Zeitstempel-Style), **Schriften
+ & Farben** (Schriftart, Schriftgröße, Chat-Farben pro Channel), **Daten-Verwaltung**
+ (Aufbewahrung, Cleanup, Export, DB-Viewer, Advanced-Tools — vorher zwischen Datenschutz und
+ Datenbank verteilt)
- Datenschutz fokussiert sich jetzt auf eine Aufgabe: den Privacy-Filter
- Der Auto-Tell-Tabs-History-Preload-Slider ist von Datenschutz nach Chat → Auto-Tell-Tabs umgezogen
- KeybindMode wohnt jetzt unter Allgemein → Eingabe statt unter Sprache
-- Vier tote Schema-Felder entfernt (alle obsolet seit der Theme-Engine in v1.1.0): `Stilüberschreiben`-Toggle,
- `Stilname`-Auswahl, alter `WindowAlpha`-Slider, ungenutztes `ShowThemeQuickPicker`
+- Vier tote Schema-Felder entfernt (alle obsolet seit der Theme-Engine in v1.1.0):
+ `Stilüberschreiben`-Toggle, `Stilname`-Auswahl, alter `WindowAlpha`-Slider, ungenutztes
+ `ShowThemeQuickPicker`
- Migration v15 → v16: alter `WindowAlpha`-Wert wird automatisch nach
- `Theme & Layout → Fenster-Style → Fenster-Transparenz` gemappt (nur wenn der Slider noch auf Default 0.85 stand, sonst
- gewinnt der User-Wert). Backup der Pre-v16-Config liegt unter `pluginConfigs/HellionChat.json.pre-v16-backup`. User
- die `Stilüberschreiben` aktiv hatten sehen einen einmaligen Hinweis-Toast
-- UX-Default-Bumps für Bestand-User mit Default-Werten: Card-Rows-Layout zurück auf Single-Line, NG+ standardmäßig
- hidden, gleiche Zeitstempel werden zusammengefasst, MaxLinesToRender auf konservativere 2500
-- Frische Installs starten mit dem Hellion-Brand-Chat-Color-Preset out-of-the-box (der First-Run-Wizard hat keine
- Preset-Wahl)
-- Hinweis zum Window-Transparenz-Slider in der Beschreibung: Dalamud's per-Window-Hamburger-Menü (oben rechts in der
- Titelleiste) bietet eigene Overrides für Deckkraft, Hintergrund-Blur, Anpinnen und Durchklick — die haben Vorrang über
- unseren Slider für das jeweilige Fenster
+ `Theme & Layout → Fenster-Style → Fenster-Transparenz` gemappt (nur wenn der Slider noch auf
+ Default 0.85 stand, sonst gewinnt der User-Wert). Backup der Pre-v16-Config liegt unter
+ `pluginConfigs/HellionChat.json.pre-v16-backup`. User die `Stilüberschreiben` aktiv hatten sehen
+ einen einmaligen Hinweis-Toast
+- UX-Default-Bumps für Bestand-User mit Default-Werten: Card-Rows-Layout zurück auf Single-Line, NG+
+ standardmäßig hidden, gleiche Zeitstempel werden zusammengefasst, MaxLinesToRender auf
+ konservativere 2500
+- Frische Installs starten mit dem Hellion-Brand-Chat-Color-Preset out-of-the-box (der
+ First-Run-Wizard hat keine Preset-Wahl)
+- Hinweis zum Window-Transparenz-Slider in der Beschreibung: Dalamud's per-Window-Hamburger-Menü
+ (oben rechts in der Titelleiste) bietet eigene Overrides für Deckkraft, Hintergrund-Blur, Anpinnen
+ und Durchklick — die haben Vorrang über unseren Slider für das jeweilige Fenster
-Pure UX-Polish, keine neuen Features. Nächster Cycle (v1.3.0): Animation-Polish (Lerps, Theme-Crossfade, Quick-Picker)
-wie ursprünglich geplant.
+Pure UX-Polish, keine neuen Features. Nächster Cycle (v1.3.0): Animation-Polish (Lerps,
+Theme-Crossfade, Quick-Picker) wie ursprünglich geplant.
diff --git a/.github/forge-posts/v1.2.3.md b/.github/forge-posts/v1.2.3.md
index f52a523..485113b 100644
--- a/.github/forge-posts/v1.2.3.md
+++ b/.github/forge-posts/v1.2.3.md
@@ -3,21 +3,23 @@ subtitle: "Theme Expansion"
versionsnatur: "Theme-Pack-Patch"
---
-- Vier neue Built-in-Themes verlängern die Auswahl im Picker — keine Engine-Änderung, keine Settings angefasst, einfach
- mehr Farboptionen
-- **Night Blue** — Royal Blue auf tiefem Marineblau. Kühles Tech-Dashboard-Mood, bewusst neutral gehalten damit es sich
- nicht mit den Brand-Themes beißt
-- **Indigo Violet** — Royal Violet auf Deep Indigo mit Türkis-Mint-Counter für Aurora-Glitter-Stimmung. Schwester von
- Event Horizon, aber dunkler und dichter; der Türkis-Akzent hält die beiden klar auseinander
-- **Forge Merchantman** — Patina-Bronze auf Workshop-Slate mit warmem Bernstein-Counter. Hellion Forge bekommt ein
- eigenes Theme im Plugin selbst — Schwester von Hellion Arctic, aber grüner und wärmer statt kaltem Cyan
+- Vier neue Built-in-Themes verlängern die Auswahl im Picker — keine Engine-Änderung, keine Settings
+ angefasst, einfach mehr Farboptionen
+- **Night Blue** — Royal Blue auf tiefem Marineblau. Kühles Tech-Dashboard-Mood, bewusst neutral
+ gehalten damit es sich nicht mit den Brand-Themes beißt
+- **Indigo Violet** — Royal Violet auf Deep Indigo mit Türkis-Mint-Counter für
+ Aurora-Glitter-Stimmung. Schwester von Event Horizon, aber dunkler und dichter; der Türkis-Akzent
+ hält die beiden klar auseinander
+- **Forge Merchantman** — Patina-Bronze auf Workshop-Slate mit warmem Bernstein-Counter. Hellion
+ Forge bekommt ein eigenes Theme im Plugin selbst — Schwester von Hellion Arctic, aber grüner und
+ wärmer statt kaltem Cyan
- **Hellion Spectrum** — Farbenblind-sichere Channel-Farben (Deuteranopie/Protanopie) auf Basis der
- Wong/Okabe-Ito-Palette. Channel-Identität bleibt erhalten (Tell pink, Yell gelb, Shout orange, Party blau, FC grün);
- die Töne sind so gewählt dass jeder Channel auch unter Rot-Grün-Schwäche klar trennbar bleibt. Deckt rund 99 % aller
- CVD-Fälle ab
-- Kein Schema-Bump, keine Migration. Das Default-Theme bleibt **Hellion Arctic**, eigene Custom-Themes laufen
- unverändert weiter
+ Wong/Okabe-Ito-Palette. Channel-Identität bleibt erhalten (Tell pink, Yell gelb, Shout orange,
+ Party blau, FC grün); die Töne sind so gewählt dass jeder Channel auch unter Rot-Grün-Schwäche
+ klar trennbar bleibt. Deckt rund 99 % aller CVD-Fälle ab
+- Kein Schema-Bump, keine Migration. Das Default-Theme bleibt **Hellion Arctic**, eigene
+ Custom-Themes laufen unverändert weiter
- Theme-Katalog wächst damit von fünf auf neun Built-ins
-Reines Theme-Pack zwischen v1.2.1 und dem nächsten Polish-Cycle. Eine Tritan-Variante (Spectrum für Blau-Gelb-Schwäche)
-kann später nachgeliefert werden, falls Bedarf kommt.
+Reines Theme-Pack zwischen v1.2.1 und dem nächsten Polish-Cycle. Eine Tritan-Variante (Spectrum für
+Blau-Gelb-Schwäche) kann später nachgeliefert werden, falls Bedarf kommt.
diff --git a/.github/forge-posts/v1.3.0.md b/.github/forge-posts/v1.3.0.md
index a64d2c9..58ab9a2 100644
--- a/.github/forge-posts/v1.3.0.md
+++ b/.github/forge-posts/v1.3.0.md
@@ -4,15 +4,17 @@ versionsnatur: "Plugin-Integration-Cycle 1"
---
- Erste Plugin-Integration eingebaut, Cycle 1 von 6 auf der Roadmap
-- **Honorific-Custom-Titles im Chat-Header** — der Titel den du in Honorific gesetzt hast erscheint jetzt links über dem
- Message-Log mit der von dir gewählten Farbe, Auto-Hide wenn Honorific nicht installiert ist oder kein Custom-Titel
- aktiv ist
-- **Krone-Icon plus Tooltip** vor dem Titel-Text, damit klar ist woher der Slot kommt ohne dass der User raten muss
-- **Neuer Integrations-Settings-Tab** mit Status-Indikator (erkannt, nicht installiert, inkompatibel) und Toggle. Plus
- Vorschau-Block der die fünf weiteren geplanten Cycles ankündigt: Kontextmenü-Aktionen, Smart Notifications
- (NotificationMaster), RP-Status-Block (Moodles und LightlessClient), ExtraChat-Channels, Quick-DM-Button
- (XIVInstantMessenger)
-- **Maintainer-Attribution** im Tab als Höflichkeits-Geste, zwei Buttons zum Honorific-Repo und zum Caraxi-Profil. Plus
- Hellion-Forge-Discord-Button für Community-Vorschläge zu künftigen Integrationen
-- Keine Migration, keine Schema-Änderung. Wer Honorific eh schon nutzt sieht den Custom-Titel automatisch sobald
- HellionChat aktualisiert
+- **Honorific-Custom-Titles im Chat-Header** — der Titel den du in Honorific gesetzt hast erscheint
+ jetzt links über dem Message-Log mit der von dir gewählten Farbe, Auto-Hide wenn Honorific nicht
+ installiert ist oder kein Custom-Titel aktiv ist
+- **Krone-Icon plus Tooltip** vor dem Titel-Text, damit klar ist woher der Slot kommt ohne dass der
+ User raten muss
+- **Neuer Integrations-Settings-Tab** mit Status-Indikator (erkannt, nicht installiert,
+ inkompatibel) und Toggle. Plus Vorschau-Block der die fünf weiteren geplanten Cycles ankündigt:
+ Kontextmenü-Aktionen, Smart Notifications (NotificationMaster), RP-Status-Block (Moodles und
+ LightlessClient), ExtraChat-Channels, Quick-DM-Button (XIVInstantMessenger)
+- **Maintainer-Attribution** im Tab als Höflichkeits-Geste, zwei Buttons zum Honorific-Repo und zum
+ Caraxi-Profil. Plus Hellion-Forge-Discord-Button für Community-Vorschläge zu künftigen
+ Integrationen
+- Keine Migration, keine Schema-Änderung. Wer Honorific eh schon nutzt sieht den Custom-Titel
+ automatisch sobald HellionChat aktualisiert
diff --git a/.github/forge-posts/v1.4.0.md b/.github/forge-posts/v1.4.0.md
index bad8c82..0e9eedb 100644
--- a/.github/forge-posts/v1.4.0.md
+++ b/.github/forge-posts/v1.4.0.md
@@ -5,19 +5,20 @@ versionsnatur: Stability-Hotfix
**Hellion Chat 1.4.0 — Critical Lifecycle Fixes**
-Erster Sub-Patch der v1.4.x Polish-Sweep-Serie. Sieben bekannte Lifecycle- und Race-Bugs aus den Audit-Pässen
-abgearbeitet, bevor Performance- und Architektur-Refactors draufkommen.
+Erster Sub-Patch der v1.4.x Polish-Sweep-Serie. Sieben bekannte Lifecycle- und Race-Bugs aus den
+Audit-Pässen abgearbeitet, bevor Performance- und Architektur-Refactors draufkommen.
-- **SQLite-Dispose** lehnt sich nicht mehr an GC-Druck zur Datei-Freigabe an, Pooling=false auf der Connection macht den
- manuellen GC.Collect überflüssig
-- **Worker-Threads** (PendingMessage, RetentionSweep) sind jetzt explizit IsBackground=true, das Plugin-Domain kann
- sauber unloaden bei XIVLauncher-Reload ohne darauf zu warten
-- **EmoteCache-Loader** von async-void auf async-Task mit shared Task-Tracker, drain-on-Dispose. Kein Schreib-Risiko
- mehr auf disposed EmoteImages-Einträge nach Plugin-Reload
+- **SQLite-Dispose** lehnt sich nicht mehr an GC-Druck zur Datei-Freigabe an, Pooling=false auf der
+ Connection macht den manuellen GC.Collect überflüssig
+- **Worker-Threads** (PendingMessage, RetentionSweep) sind jetzt explizit IsBackground=true, das
+ Plugin-Domain kann sauber unloaden bei XIVLauncher-Reload ohne darauf zu warten
+- **EmoteCache-Loader** von async-void auf async-Task mit shared Task-Tracker, drain-on-Dispose.
+ Kein Schreib-Risiko mehr auf disposed EmoteImages-Einträge nach Plugin-Reload
- **DisposeAsync-Timeout** (10s) warnt jetzt laut statt silent zu failen
-- **Plugin-Dispose** flushed pending DeferredSave bevor Services abgebaut werden, Settings-Änderungen aus den letzten
- Frames vor Disable überleben jetzt zuverlässig
-- **v13→v14 Config-Migration** liest pre-v13-Backup und überträgt HellionThemeWindowOpacity in das neue
- WindowOpacity-Feld statt auf 0.85 zurückzufallen
+- **Plugin-Dispose** flushed pending DeferredSave bevor Services abgebaut werden,
+ Settings-Änderungen aus den letzten Frames vor Disable überleben jetzt zuverlässig
+- **v13→v14 Config-Migration** liest pre-v13-Backup und überträgt HellionThemeWindowOpacity in das
+ neue WindowOpacity-Feld statt auf 0.85 zurückzufallen
-Keine Schema-Bumps, keine User-sichtbaren Funktions-Änderungen außer dass Reload und Shutdown spürbar sauberer laufen.
+Keine Schema-Bumps, keine User-sichtbaren Funktions-Änderungen außer dass Reload und Shutdown
+spürbar sauberer laufen.
diff --git a/.github/forge-posts/v1.4.1.md b/.github/forge-posts/v1.4.1.md
index 4d30828..156c40a 100644
--- a/.github/forge-posts/v1.4.1.md
+++ b/.github/forge-posts/v1.4.1.md
@@ -5,23 +5,25 @@ versionsnatur: Performance-Patch
**Hellion Chat 1.4.1 — Theme Engine Performance**
-Zweiter Sub-Patch der v1.4.x Polish-Sweep-Serie. Heap-Pressure aus dem Theme-Engine-Render-Pfad eliminiert,
-Custom-Theme- Hot-Reload überlebt transiente File-Locks beim Editor-Save. Plus zehnter Built-In und überarbeitete
-Author-Credits.
+Zweiter Sub-Patch der v1.4.x Polish-Sweep-Serie. Heap-Pressure aus dem Theme-Engine-Render-Pfad
+eliminiert, Custom-Theme- Hot-Reload überlebt transiente File-Locks beim Editor-Save. Plus zehnter
+Built-In und überarbeitete Author-Credits.
-- **ABGR-Cache auf den Theme-Records.** Beim Theme-Register (Built-In oder Custom) werden alle Color-Slots einmalig in
- ABGR-Pack-Form vor-konvertiert. HellionStyle.PushGlobal liest aus dem Cache statt pro Slot pro Frame durch
- ColourUtil.RgbaToAbgr zu jagen. Real gemessene Frame-Time-Recovery: **~13 %** in typischer Render-Szene
- (Plan-Erwartung war 2-6 % konservativ, real ~10-15 %)
-- **Custom-Theme File-Lock-Härtung.** Wenn der User ein Theme-JSON gerade speichert während HellionChat reloaden will,
- fängt der Loader jetzt explizit Sharing-Violation und Lock-Violation ab. Last-Known-Good-Snapshot bleibt im Picker,
- beim nächsten Tick wird automatisch retry'd — vorher fiel das Theme aus der Liste bis zum Plugin-Reload
-- **Defensive Cache-Refresh beim Theme-Switch.** Falls ein Theme auf einem alten Pfad ohne Cache-Fill in den Speicher
- gekommen ist, holt Switch() das beim Anwenden nach
-- **Synthwave Sunset als zehnter Built-In.** Hot Magenta + Cyan auf Mitternachts-Violett, 80s-Neon-Grid-Vibes für
- Late-Night-Raids
-- **Author-Credits konsolidiert.** Brand-Themes laufen jetzt unter „Hellion Forge". Mint Grove und Forge Merchantman
- werden Carla Beleandis als Community-Geste zugeschrieben.
+- **ABGR-Cache auf den Theme-Records.** Beim Theme-Register (Built-In oder Custom) werden alle
+ Color-Slots einmalig in ABGR-Pack-Form vor-konvertiert. HellionStyle.PushGlobal liest aus dem
+ Cache statt pro Slot pro Frame durch ColourUtil.RgbaToAbgr zu jagen. Real gemessene
+ Frame-Time-Recovery: **~13 %** in typischer Render-Szene (Plan-Erwartung war 2-6 % konservativ,
+ real ~10-15 %)
+- **Custom-Theme File-Lock-Härtung.** Wenn der User ein Theme-JSON gerade speichert während
+ HellionChat reloaden will, fängt der Loader jetzt explizit Sharing-Violation und Lock-Violation
+ ab. Last-Known-Good-Snapshot bleibt im Picker, beim nächsten Tick wird automatisch retry'd —
+ vorher fiel das Theme aus der Liste bis zum Plugin-Reload
+- **Defensive Cache-Refresh beim Theme-Switch.** Falls ein Theme auf einem alten Pfad ohne
+ Cache-Fill in den Speicher gekommen ist, holt Switch() das beim Anwenden nach
+- **Synthwave Sunset als zehnter Built-In.** Hot Magenta + Cyan auf Mitternachts-Violett,
+ 80s-Neon-Grid-Vibes für Late-Night-Raids
+- **Author-Credits konsolidiert.** Brand-Themes laufen jetzt unter „Hellion Forge". Mint Grove und
+ Forge Merchantman werden Carla Beleandis als Community-Geste zugeschrieben.
-Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames in Theme-getrieben rendernden
-Szenen merklich glatter laufen und ein neues Theme im Picker steht.
+Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames in
+Theme-getrieben rendernden Szenen merklich glatter laufen und ein neues Theme im Picker steht.
diff --git a/.github/forge-posts/v1.4.10.md b/.github/forge-posts/v1.4.10.md
index 8053b09..07a5adb 100644
--- a/.github/forge-posts/v1.4.10.md
+++ b/.github/forge-posts/v1.4.10.md
@@ -3,34 +3,25 @@ subtitle: Symbol-Picker und Tell-History Fix
versionsnatur: Feature-Patch + Hotfix
---
-- Symbol-Picker im Chat-Eingang: ein kleiner Smile-Button links neben
- dem Kanal-Indikator öffnet ein Popup mit zwei Tabs. Der erste listet
- alle 161 FFXIV-PUA-Glyphen (Dalamuds SeIconChar); der zweite trägt
- 97 verifizierte BMP-Symbole (Latin-Marken, Währungen, das ganze
- griechische Alphabet, Geometrie, Spielkarten, Noten) — jedes davon
- über `/echo` und `/say` in einer vierrundigen Whitelist-Probe
- durchgereicht, damit der Channel-Render dem entspricht, was der
- Picker anzeigt. Klick fügt das Symbol an der Cursor-Position ein,
- Multi-Insert lässt das Popup offen, eine Recent-Used-Leiste zeigt
- die letzten sechzehn Picks über beide Tabs. Toggle in Settings →
- Chat → Nachrichten-Verhalten, Default an.
-- Verlauf in angepinnten Tell-Tabs lädt wieder vollständig: ein
- versteckter 500-Zeilen-Scan-Cap in PreloadHistory hat das
- User-Setting `AutoTellTabsHistoryPreload` überschrieben, wodurch
- weniger-frequente Tell-Partner ihren Backlog verloren haben sobald
- die Scan-Schicht mit anderen Chat-Partnern voll lief. Cap ist raus,
- der Index auf `(Receiver, Date)` hält die Query schnell.
-- Slash-Command-Teardown: /hellion, /hellionView, /hellionDebugger
- (und im Debug-Build /hellionSeString) sind als private Felder
- gecached. Plugin-Dispose detached die echte Registrierung, statt
- mit identischen Args neu zu registrieren — schließt eine latente
+- Symbol-Picker im Chat-Eingang: ein kleiner Smile-Button links neben dem Kanal-Indikator öffnet ein
+ Popup mit zwei Tabs. Der erste listet alle 161 FFXIV-PUA-Glyphen (Dalamuds SeIconChar); der zweite
+ trägt 97 verifizierte BMP-Symbole (Latin-Marken, Währungen, das ganze griechische Alphabet,
+ Geometrie, Spielkarten, Noten) — jedes davon über `/echo` und `/say` in einer vierrundigen
+ Whitelist-Probe durchgereicht, damit der Channel-Render dem entspricht, was der Picker anzeigt.
+ Klick fügt das Symbol an der Cursor-Position ein, Multi-Insert lässt das Popup offen, eine
+ Recent-Used-Leiste zeigt die letzten sechzehn Picks über beide Tabs. Toggle in Settings → Chat →
+ Nachrichten-Verhalten, Default an.
+- Verlauf in angepinnten Tell-Tabs lädt wieder vollständig: ein versteckter 500-Zeilen-Scan-Cap in
+ PreloadHistory hat das User-Setting `AutoTellTabsHistoryPreload` überschrieben, wodurch
+ weniger-frequente Tell-Partner ihren Backlog verloren haben sobald die Scan-Schicht mit anderen
+ Chat-Partnern voll lief. Cap ist raus, der Index auf `(Receiver, Date)` hält die Query schnell.
+- Slash-Command-Teardown: /hellion, /hellionView, /hellionDebugger (und im Debug-Build
+ /hellionSeString) sind als private Felder gecached. Plugin-Dispose detached die echte
+ Registrierung, statt mit identischen Args neu zu registrieren — schließt eine latente
Wartungs-Falle aus v1.4.9.
-- v1.4.x-Polish-Sweep endet hier. Der ImGuiListClipper-Refactor von
- der v1.4.10-Reserve-Liste wurde gecancelt, nachdem der Cross-
- Plattform-Smoke gezeigt hat dass das Scroll-Gummi ein Wine/Linux-
- Quirk ist — Windows-User haben es nie gesehen. Spike dafür kommt in
- einem späteren Patch. Nächster Major-Cycle ist v1.5.0 mit der
- DI-Container-Adoption (`Microsoft.Extensions.Hosting` +
+- v1.4.x-Polish-Sweep endet hier. Der ImGuiListClipper-Refactor von der v1.4.10-Reserve-Liste wurde
+ gecancelt, nachdem der Cross- Plattform-Smoke gezeigt hat dass das Scroll-Gummi ein Wine/Linux-
+ Quirk ist — Windows-User haben es nie gesehen. Spike dafür kommt in einem späteren Patch. Nächster
+ Major-Cycle ist v1.5.0 mit der DI-Container-Adoption (`Microsoft.Extensions.Hosting` +
`ILogger`) nach dem Lightless-Vorbild.
-- Migration v17 unverändert: kein Schema-Bump, kein
- Config-Migrations-Aufwand.
+- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-Aufwand.
diff --git a/.github/forge-posts/v1.4.2.md b/.github/forge-posts/v1.4.2.md
index dc9f0f6..11ff2bc 100644
--- a/.github/forge-posts/v1.4.2.md
+++ b/.github/forge-posts/v1.4.2.md
@@ -5,25 +5,27 @@ versionsnatur: Performance-Patch
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
-Dritter Sub-Patch der v1.4.x Polish-Sweep-Serie. Drei Per-Frame-Allokations-Quellen aus dem ChatLogWindow-Render- Pfad
-und der Settings-StatusBar eliminiert.
+Dritter Sub-Patch der v1.4.x Polish-Sweep-Serie. Drei Per-Frame-Allokations-Quellen aus dem
+ChatLogWindow-Render- Pfad und der Settings-StatusBar eliminiert.
-- **Card-Mode-Border-Loop entlastet.** DrawMessages hebt Theme, DrawList, Window-Left, Window-Right und die ABGR-
- Border-Color einmalig vor den Per-Message-Loop. Bei 100 sichtbaren Messages sind das gut 500 redundante P/Invokes und
- Property-Reads, die der Hoist eliminiert. Pop-Out- Heavy-Setups (mehrere parallele Chat-Windows) profitieren
- proportional, weil der Hoist pro DrawMessages-Call greift, also pro Window
-- **Auto-Tell Tab-Tint und Icon gecached.** Die Hash-Color- Berechnung für Auto-Tell-Tabs lief pro Tab pro Frame, mit
- zwei String-Allokationen pro Tab (eine für Tint-Hash, eine für Icon-Hash). Der neue TabTintCache liest pre-computed
- Werte aus dem Tab und rechnet nur neu wenn das Tell-Target drifted. Beide Caches haben separate Validation-Keys, also
- keine Cross-Invalidation zwischen Tint- und Icon-Pfad. AutoTellTabTint selbst bleibt pure Hash-Helper, weiterhin ohne
- Tab-Awareness
-- **StatusBar-Aggregation hinter Cache-Gate.** Die Status- Leiste am unteren Window-Rand summiert die Tab-Message-
- Counts und zählt die Auto-Tell-Tabs pro Frame. Der Cache- Gate (1 Sekunde) lag bisher hinter den LINQ-Pfaden, also
- liefen Sum und Count trotzdem pro Frame. Jetzt vor dem Gate, plus die LINQ-Pfade durch eine Single-Pass-Foreach
- ersetzt. Die Aggregation läuft auf etwa 1 % der Frames
+- **Card-Mode-Border-Loop entlastet.** DrawMessages hebt Theme, DrawList, Window-Left, Window-Right
+ und die ABGR- Border-Color einmalig vor den Per-Message-Loop. Bei 100 sichtbaren Messages sind das
+ gut 500 redundante P/Invokes und Property-Reads, die der Hoist eliminiert. Pop-Out- Heavy-Setups
+ (mehrere parallele Chat-Windows) profitieren proportional, weil der Hoist pro DrawMessages-Call
+ greift, also pro Window
+- **Auto-Tell Tab-Tint und Icon gecached.** Die Hash-Color- Berechnung für Auto-Tell-Tabs lief pro
+ Tab pro Frame, mit zwei String-Allokationen pro Tab (eine für Tint-Hash, eine für Icon-Hash). Der
+ neue TabTintCache liest pre-computed Werte aus dem Tab und rechnet nur neu wenn das Tell-Target
+ drifted. Beide Caches haben separate Validation-Keys, also keine Cross-Invalidation zwischen Tint-
+ und Icon-Pfad. AutoTellTabTint selbst bleibt pure Hash-Helper, weiterhin ohne Tab-Awareness
+- **StatusBar-Aggregation hinter Cache-Gate.** Die Status- Leiste am unteren Window-Rand summiert
+ die Tab-Message- Counts und zählt die Auto-Tell-Tabs pro Frame. Der Cache- Gate (1 Sekunde) lag
+ bisher hinter den LINQ-Pfaden, also liefen Sum und Count trotzdem pro Frame. Jetzt vor dem Gate,
+ plus die LINQ-Pfade durch eine Single-Pass-Foreach ersetzt. Die Aggregation läuft auf etwa 1 % der
+ Frames
-Realistische Frame-Time-Recovery: 2-5 % in typischen Szenen, Pop-Out-Heavy-Setups potenziell mehr durch die Card-Border-
-Multiplikation pro Window.
+Realistische Frame-Time-Recovery: 2-5 % in typischen Szenen, Pop-Out-Heavy-Setups potenziell mehr
+durch die Card-Border- Multiplikation pro Window.
-Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames im Chat-Log und in der
-Settings-Statusleiste merklich glatter laufen.
+Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames im Chat-Log
+und in der Settings-Statusleiste merklich glatter laufen.
diff --git a/.github/forge-posts/v1.4.3.md b/.github/forge-posts/v1.4.3.md
index cd3d181..47f809d 100644
--- a/.github/forge-posts/v1.4.3.md
+++ b/.github/forge-posts/v1.4.3.md
@@ -5,25 +5,29 @@ versionsnatur: Architecture-Refactor
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover**
-Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin- Lifecycle auf Dalamud's `IAsyncDalamudPlugin`-API migriert und
-das Custom-Repo zieht von GitHub auf Gitea um.
+Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin- Lifecycle auf Dalamud's
+`IAsyncDalamudPlugin`-API migriert und das Custom-Repo zieht von GitHub auf Gitea um.
-- **Async-Plugin-Architektur.** Konstruktor übernimmt nur noch die Bootstrap-Essentials (Config-Load, Language-Init,
- Conflict-Detection). Migrationen, Service-Allokationen, Window-Konstruktion und Hook-Subscription wandern in
- LoadAsync, sodass Dalamud die UI während der schweren Arbeit responsive halten kann. Per-Line-CaptureFailure in
- DisposeAsync mirrort LightlessSync's Pattern, plus Idempotency-Guard gegen Reload-Races
-- **Custom-Repo-URL umgezogen auf Gitea.** Bestehende Tester müssen einmalig in XIVLauncher die Custom-Repo-URL auf
- `https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json` umstellen, dann
- XIVLauncher neu starten. Das alte GitHub-Repo bleibt als eingefrorener v1.4.2-Snapshot stehen und wird nicht mehr
- aktualisiert
-- **Schema-Gate statt Migrations-Kette.** Die v9 → v16 Migrationen sind raus, ersetzt durch einen harten Schema-Check in
- Phase 1. Configs auf Schema v16+ laden direkt; ältere Configs (vor v1.2.1) bekommen jetzt eine klare „install v1.4.2
- first"-Fehlermeldung statt eines impliziten Migrations-Pfads
-- **AutoTranslate-Cache läuft im Hintergrund.** Der Cache füllt sich jetzt fire-and-forget statt blockierend im
- Plugin-Load. Trade-off: die erste Auto-Translate-Nutzung einer Session kann einen kurzen Hitch haben, dafür kein
- 300-ms-Block beim Plugin-Start
-- **Plugin-Load-Zeit ehrlich.** Median 3,7 s über fünf Reloads, vergleichbar mit v1.4.2. Der Async-Refactor ist
- Foundation für künftige Lazy-Init-Optimierungen (v1.4.4) und Code-Architektur-Hygiene, kein direkter User-spürbarer
- Speed-Win in dieser Release
+- **Async-Plugin-Architektur.** Konstruktor übernimmt nur noch die Bootstrap-Essentials
+ (Config-Load, Language-Init, Conflict-Detection). Migrationen, Service-Allokationen,
+ Window-Konstruktion und Hook-Subscription wandern in LoadAsync, sodass Dalamud die UI während der
+ schweren Arbeit responsive halten kann. Per-Line-CaptureFailure in DisposeAsync mirrort
+ LightlessSync's Pattern, plus Idempotency-Guard gegen Reload-Races
+- **Custom-Repo-URL umgezogen auf Gitea.** Bestehende Tester müssen einmalig in XIVLauncher die
+ Custom-Repo-URL auf
+ `https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
+ umstellen, dann XIVLauncher neu starten. Das alte GitHub-Repo bleibt als eingefrorener
+ v1.4.2-Snapshot stehen und wird nicht mehr aktualisiert
+- **Schema-Gate statt Migrations-Kette.** Die v9 → v16 Migrationen sind raus, ersetzt durch einen
+ harten Schema-Check in Phase 1. Configs auf Schema v16+ laden direkt; ältere Configs (vor v1.2.1)
+ bekommen jetzt eine klare „install v1.4.2 first"-Fehlermeldung statt eines impliziten
+ Migrations-Pfads
+- **AutoTranslate-Cache läuft im Hintergrund.** Der Cache füllt sich jetzt fire-and-forget statt
+ blockierend im Plugin-Load. Trade-off: die erste Auto-Translate-Nutzung einer Session kann einen
+ kurzen Hitch haben, dafür kein 300-ms-Block beim Plugin-Start
+- **Plugin-Load-Zeit ehrlich.** Median 3,7 s über fünf Reloads, vergleichbar mit v1.4.2. Der
+ Async-Refactor ist Foundation für künftige Lazy-Init-Optimierungen (v1.4.4) und
+ Code-Architektur-Hygiene, kein direkter User-spürbarer Speed-Win in dieser Release
-Keine User-sichtbaren Funktions-Änderungen außer dem Repo-URL-Update. Settings, Themes und Tabs bleiben unangetastet.
+Keine User-sichtbaren Funktions-Änderungen außer dem Repo-URL-Update. Settings, Themes und Tabs
+bleiben unangetastet.
diff --git a/.github/forge-posts/v1.4.4.md b/.github/forge-posts/v1.4.4.md
index a2530d7..3469169 100644
--- a/.github/forge-posts/v1.4.4.md
+++ b/.github/forge-posts/v1.4.4.md
@@ -5,30 +5,33 @@ versionsnatur: Wartung und Robustheit
**Hellion Chat 1.4.4 — Threading- und IPC-Sicherheits-Politur**
-Fünfter Sub-Patch der v1.4.x Polish-Sweep-Serie. Threading-Annahmen werden explizit pro Methode dokumentiert, ein
-Hot-Path-Lock im Auto-Tell-Tab-Counter fällt weg, IPC-Cleanup wird sichtbar wenn er fehlschlägt und der Privacy-Filter
-spricht jetzt bei unbekannten ChatTypes.
+Fünfter Sub-Patch der v1.4.x Polish-Sweep-Serie. Threading-Annahmen werden explizit pro Methode
+dokumentiert, ein Hot-Path-Lock im Auto-Tell-Tab-Counter fällt weg, IPC-Cleanup wird sichtbar wenn
+er fehlschlägt und der Privacy-Filter spricht jetzt bei unbekannten ChatTypes.
-- **AutoTellTabsService Hot-Path-Lock entfernt.** `ActiveTempTabCount` hat bisher pro Render-Frame ein LINQ-Count unter
- einem Lock gemacht. Jetzt läuft das über einen Interlocked-Counter der parallel zur Tabs-Liste mitgeführt wird,
- inklusive Resync-Hook für den Snapshot-Restore-Pfad in `SaveConfig`. Plus Pure-Helper-Test-Mirror in der Build-Suite
- damit die Atomicity-Semantik nicht versehentlich wegrefactored wird
-- **HonorificService selbst-dokumentierende Threading-Banner.** Statt eines Block-Comments am Klassen-Ende hat jede
- IPC-Callback-Methode jetzt einen 1-Zeilen-Banner darüber, der den Thread-Kontext direkt am Call-Site benennt
- (framework only, framework scheduled, any). Mehr Hilfe für künftige Reviews als ein abstraktes Threading-Kapitel
-- **Unsubscribe-Failure ist jetzt sichtbar.** `TryUnsubscribe` hat ein Honorific-Unsubscribe-Failure bisher als Debug
- geloggt, was bei Standard-Loglevel verschluckt wurde. Eine geleakte Subscription kann den Service über Plugin-Reloads
- hinweg leben lassen, also läuft der Log jetzt auf Warning
-- **AutoTranslate-Warmup blockiert den Plugin-Unload nicht mehr.** Der Cache-Warmup-Thread war ohne `IsBackground=true`
- unterwegs, was den Unload um 100-300 ms verzögern konnte. Pattern-Match zu MessageManager und RetentionSweep (beide
- seit v1.4.0)
-- **Privacy-Filter loggt unbekannte ChatTypes.** Wenn FFXIV durch einen Patch einen neuen ChatType einführt der weder in
- der Whitelist noch in den Defaults steht, wird er bisher silent durch den Failsafe geleitet. Jetzt loggt der Filter
- einmalig pro Runtime eine Warning mit dem Type und dem Failsafe-Wert. Dedup über ein NonSerialized-HashSet, also kein
- Log-Spam
-- **Default-Flip für neue Installationen.** `PrivacyPersistUnknownChannels` startet bei neuen Configs jetzt auf `true`,
- damit ein Patch-bedingt neuer ChatType nicht stillschweigend gedroppt wird bevor der User entscheiden kann. Bestehende
- Configs behalten ihre Wahl, weil der Deserializer den Initializer überschreibt. Keine Migration, kein Schema-Bump
+- **AutoTellTabsService Hot-Path-Lock entfernt.** `ActiveTempTabCount` hat bisher pro Render-Frame
+ ein LINQ-Count unter einem Lock gemacht. Jetzt läuft das über einen Interlocked-Counter der
+ parallel zur Tabs-Liste mitgeführt wird, inklusive Resync-Hook für den Snapshot-Restore-Pfad in
+ `SaveConfig`. Plus Pure-Helper-Test-Mirror in der Build-Suite damit die Atomicity-Semantik nicht
+ versehentlich wegrefactored wird
+- **HonorificService selbst-dokumentierende Threading-Banner.** Statt eines Block-Comments am
+ Klassen-Ende hat jede IPC-Callback-Methode jetzt einen 1-Zeilen-Banner darüber, der den
+ Thread-Kontext direkt am Call-Site benennt (framework only, framework scheduled, any). Mehr Hilfe
+ für künftige Reviews als ein abstraktes Threading-Kapitel
+- **Unsubscribe-Failure ist jetzt sichtbar.** `TryUnsubscribe` hat ein Honorific-Unsubscribe-Failure
+ bisher als Debug geloggt, was bei Standard-Loglevel verschluckt wurde. Eine geleakte Subscription
+ kann den Service über Plugin-Reloads hinweg leben lassen, also läuft der Log jetzt auf Warning
+- **AutoTranslate-Warmup blockiert den Plugin-Unload nicht mehr.** Der Cache-Warmup-Thread war ohne
+ `IsBackground=true` unterwegs, was den Unload um 100-300 ms verzögern konnte. Pattern-Match zu
+ MessageManager und RetentionSweep (beide seit v1.4.0)
+- **Privacy-Filter loggt unbekannte ChatTypes.** Wenn FFXIV durch einen Patch einen neuen ChatType
+ einführt der weder in der Whitelist noch in den Defaults steht, wird er bisher silent durch den
+ Failsafe geleitet. Jetzt loggt der Filter einmalig pro Runtime eine Warning mit dem Type und dem
+ Failsafe-Wert. Dedup über ein NonSerialized-HashSet, also kein Log-Spam
+- **Default-Flip für neue Installationen.** `PrivacyPersistUnknownChannels` startet bei neuen
+ Configs jetzt auf `true`, damit ein Patch-bedingt neuer ChatType nicht stillschweigend gedroppt
+ wird bevor der User entscheiden kann. Bestehende Configs behalten ihre Wahl, weil der Deserializer
+ den Initializer überschreibt. Keine Migration, kein Schema-Bump
-Keine User-sichtbaren Funktions-Änderungen außer dem Default-Flip für neue Installationen. Settings, Themes, Tabs und
-das Privacy-Verhalten für Bestand bleiben unangetastet.
+Keine User-sichtbaren Funktions-Änderungen außer dem Default-Flip für neue Installationen. Settings,
+Themes, Tabs und das Privacy-Verhalten für Bestand bleiben unangetastet.
diff --git a/.github/forge-posts/v1.4.5.md b/.github/forge-posts/v1.4.5.md
index 0016a9a..e74bd54 100644
--- a/.github/forge-posts/v1.4.5.md
+++ b/.github/forge-posts/v1.4.5.md
@@ -5,24 +5,27 @@ versionsnatur: UX-Polish-Cycle
**Hellion Chat 1.4.5 — UX und Robustheit**
-Sechster Sub-Patch der v1.4.x Polish-Sweep-Serie. Render-Fehler im Chat-Fenster werden jetzt sichtbar, der
-First-Run-Wizard hat eine explizite Cancel-Schaltfläche, der Eingabe-Verlauf bleibt nicht mehr über Plugin-Reloads
-hinweg liegen, und die Statusleiste klippt in schmalen Fenstern nicht mehr.
+Sechster Sub-Patch der v1.4.x Polish-Sweep-Serie. Render-Fehler im Chat-Fenster werden jetzt
+sichtbar, der First-Run-Wizard hat eine explizite Cancel-Schaltfläche, der Eingabe-Verlauf bleibt
+nicht mehr über Plugin-Reloads hinweg liegen, und die Statusleiste klippt in schmalen Fenstern nicht
+mehr.
-- **Fehler-Benachrichtigung im Chat-Fenster.** Wenn ein Render-Fehler in `DrawChatLog` auftritt, zeigt das Plugin jetzt
- eine einmalige Warning-Notification mit Verweis aufs `/xllog`, statt das Fenster stillschweigend leer zu lassen. Der
- Stack-Trace selbst geht weiter via `Plugin.Log.Error` ins Logfile. De-Dup über Per-Session-Bool, damit ein
- wiederkehrender Fehler die Notification-Stack nicht pro Frame neu vollkippt
-- **First-Run-Wizard trennt Accept und Close.** `OnClose` setzt nicht mehr stillschweigend `FirstRunCompleted=true`,
- also lässt das X den Wizard schwebend zurück und er kommt beim nächsten Plugin-Reload wieder. Eine neue „Später —
- Defaults behalten"-Schaltfläche im Footer ist der explizite Weg, ohne Profil-Auswahl rauszukommen. Strings bilingual
- EN+DE plus Tooltip
-- **Eingabe-Verlauf wird beim Plugin-Reload geleert.** `InputHistoryService.Reset` hängt jetzt in `Plugin.DisposeAsync`
- neben den anderen Pure-Memory-Cleanups, damit der statische Zustand aus der vorigen Session den nächsten Load nicht
- mehr erbt
-- **Statusleiste klippt nicht mehr.** Der rechtsbündige Versions-Slot wird ausgeblendet wenn die Chat-Window-Breite
- abzüglich Versions-Text unter 200 px fällt — vorher überlappte er die vier linken Slots. Ab ausreichender Breite
- taucht der Slot wieder auf
-- **Intern:** `FontManager` fällt auf System-Font zurück wenn die eingebettete Hellion-Font-Resource fehlt
- (Broken-csproj-Pfad, nie ein Produktions-Build), plus expliziter Session-Only-Invariant-Kommentar für Auto-Tell-Tabs
- in `Plugin.cs:167-168` mit einem TempTabCounter-Init-Pin in der Build-Suite. Kein Schema-Bump, keine Migration
+- **Fehler-Benachrichtigung im Chat-Fenster.** Wenn ein Render-Fehler in `DrawChatLog` auftritt,
+ zeigt das Plugin jetzt eine einmalige Warning-Notification mit Verweis aufs `/xllog`, statt das
+ Fenster stillschweigend leer zu lassen. Der Stack-Trace selbst geht weiter via `Plugin.Log.Error`
+ ins Logfile. De-Dup über Per-Session-Bool, damit ein wiederkehrender Fehler die Notification-Stack
+ nicht pro Frame neu vollkippt
+- **First-Run-Wizard trennt Accept und Close.** `OnClose` setzt nicht mehr stillschweigend
+ `FirstRunCompleted=true`, also lässt das X den Wizard schwebend zurück und er kommt beim nächsten
+ Plugin-Reload wieder. Eine neue „Später — Defaults behalten"-Schaltfläche im Footer ist der
+ explizite Weg, ohne Profil-Auswahl rauszukommen. Strings bilingual EN+DE plus Tooltip
+- **Eingabe-Verlauf wird beim Plugin-Reload geleert.** `InputHistoryService.Reset` hängt jetzt in
+ `Plugin.DisposeAsync` neben den anderen Pure-Memory-Cleanups, damit der statische Zustand aus der
+ vorigen Session den nächsten Load nicht mehr erbt
+- **Statusleiste klippt nicht mehr.** Der rechtsbündige Versions-Slot wird ausgeblendet wenn die
+ Chat-Window-Breite abzüglich Versions-Text unter 200 px fällt — vorher überlappte er die vier
+ linken Slots. Ab ausreichender Breite taucht der Slot wieder auf
+- **Intern:** `FontManager` fällt auf System-Font zurück wenn die eingebettete Hellion-Font-Resource
+ fehlt (Broken-csproj-Pfad, nie ein Produktions-Build), plus expliziter
+ Session-Only-Invariant-Kommentar für Auto-Tell-Tabs in `Plugin.cs:167-168` mit einem
+ TempTabCounter-Init-Pin in der Build-Suite. Kein Schema-Bump, keine Migration
diff --git a/.github/forge-posts/v1.4.6.md b/.github/forge-posts/v1.4.6.md
index e7f4e39..cfaebd8 100644
--- a/.github/forge-posts/v1.4.6.md
+++ b/.github/forge-posts/v1.4.6.md
@@ -3,31 +3,34 @@ subtitle: Code Hygiene and Refactor
versionsnatur: Maintenance-Cycle
---
-Wartungs-Patch ohne User-sichtbare Änderungen. Saubere Code-Basis als Vorbereitung auf das v1.4.7-Backlog-Cleanup, plus
-zwei geerbte Bugfixes aus dem ChatTwo-Upstream `f35b7d3`.
+Wartungs-Patch ohne User-sichtbare Änderungen. Saubere Code-Basis als Vorbereitung auf das
+v1.4.7-Backlog-Cleanup, plus zwei geerbte Bugfixes aus dem ChatTwo-Upstream `f35b7d3`.
-- **preflight.sh härter**: csharpier-Reflow-Check (Block E) und markdownlint (Block F) laufen jetzt im Pre-Push-Gate,
- statt erst beim Pre-Merge-Review aufzufallen.
-- **FontManager-Fallback robuster**: Atlas-Toolkit-Throws aus kaputten Font-Configs (IO, InvalidOperation,
- ArgumentException) fallen jetzt zuverlässig auf NotoSansCjkRegular, statt den Atlas-Build mitzureißen. Der
- Exception-Typ wird im Log mitgegeben für die Diagnose.
-- **URL-Validation beim Plugin-Load**: BrandingLinks (5 URLs) und IntegrationLinks (2 URLs) werden via
- `[ModuleInitializer]` geprüft. Ein Tippfehler bei einer künftigen URL-Rotation wirft jetzt sofort beim Plugin-Load,
- statt still beim Klick zu scheitern.
-- **Cherry-Pick aus ChatTwo `f35b7d3`** — Memory-Leak in `Chat.SetChannel`: der native `Utf8String` wird jetzt auch dann
- freigegeben, wenn der Linkshell-Check den Channel ablehnt (vorher gefangen im early-return).
-- **Cherry-Pick aus ChatTwo `f35b7d3`** — `Tab.Clone()` Deep-cloned jetzt `UsedChannel` und `TellTarget`. Vorher
- Reference-Share-Bug: PopOut- und Temp-Tabs mutierten sich gegenseitig.
+- **preflight.sh härter**: csharpier-Reflow-Check (Block E) und markdownlint (Block F) laufen jetzt
+ im Pre-Push-Gate, statt erst beim Pre-Merge-Review aufzufallen.
+- **FontManager-Fallback robuster**: Atlas-Toolkit-Throws aus kaputten Font-Configs (IO,
+ InvalidOperation, ArgumentException) fallen jetzt zuverlässig auf NotoSansCjkRegular, statt den
+ Atlas-Build mitzureißen. Der Exception-Typ wird im Log mitgegeben für die Diagnose.
+- **URL-Validation beim Plugin-Load**: BrandingLinks (5 URLs) und IntegrationLinks (2 URLs) werden
+ via `[ModuleInitializer]` geprüft. Ein Tippfehler bei einer künftigen URL-Rotation wirft jetzt
+ sofort beim Plugin-Load, statt still beim Klick zu scheitern.
+- **Cherry-Pick aus ChatTwo `f35b7d3`** — Memory-Leak in `Chat.SetChannel`: der native `Utf8String`
+ wird jetzt auch dann freigegeben, wenn der Linkshell-Check den Channel ablehnt (vorher gefangen im
+ early-return).
+- **Cherry-Pick aus ChatTwo `f35b7d3`** — `Tab.Clone()` Deep-cloned jetzt `UsedChannel` und
+ `TellTarget`. Vorher Reference-Share-Bug: PopOut- und Temp-Tabs mutierten sich gegenseitig.
- **Aktive-Tab-Underline pixel-perfect bei DPI-Scaling**: Die Underline-Pill skaliert jetzt mit
- `ImGuiHelpers.GlobalScale` und rundet die DrawList-Koordinaten auf physische Pixel. Kein Sub-Pixel-Blur mehr auf
- 125/150%-Setups.
-- **IconButton-Width-Fix**: der manuelle `width - 2 * CellPadding.X`-Subtract verlor den HUD-Scale (Padding skaliert,
- der raw int nicht). Gemessene Breite läuft jetzt unverändert durch.
-- **Test-Isolation für MessageStore**: `Dalamud.Utility.Util`-Surface (IsWine, OpenLink) läuft jetzt durch eine
- `IPlatformUtil`-Indirektion. MessageStores `IsWine`-Probe ist isoliert testbar in der Build-Suite. Plus:
- HellionStyle-ChildBgAlpha als Pure-Helper extrahiert, Plugin.SaveConfig kopiert nur Session-Tabs statt der ganzen
- Tab-Liste, SettingsOverview cached den DrawList einmal pro Frame.
-- **Built-in-Theme-Roster**: Crystal Nocturne (Royal Sapphire + Electric Magenta auf Obsidian, von CRYSTALLITE) ersetzt
- Moonlit Bloom. User mit Moonlit Bloom als aktivem Theme fallen beim ersten Plugin-Load auf Hellion Arctic zurück.
+ `ImGuiHelpers.GlobalScale` und rundet die DrawList-Koordinaten auf physische Pixel. Kein
+ Sub-Pixel-Blur mehr auf 125/150%-Setups.
+- **IconButton-Width-Fix**: der manuelle `width - 2 * CellPadding.X`-Subtract verlor den HUD-Scale
+ (Padding skaliert, der raw int nicht). Gemessene Breite läuft jetzt unverändert durch.
+- **Test-Isolation für MessageStore**: `Dalamud.Utility.Util`-Surface (IsWine, OpenLink) läuft jetzt
+ durch eine `IPlatformUtil`-Indirektion. MessageStores `IsWine`-Probe ist isoliert testbar in der
+ Build-Suite. Plus: HellionStyle-ChildBgAlpha als Pure-Helper extrahiert, Plugin.SaveConfig kopiert
+ nur Session-Tabs statt der ganzen Tab-Liste, SettingsOverview cached den DrawList einmal pro
+ Frame.
+- **Built-in-Theme-Roster**: Crystal Nocturne (Royal Sapphire + Electric Magenta auf Obsidian, von
+ CRYSTALLITE) ersetzt Moonlit Bloom. User mit Moonlit Bloom als aktivem Theme fallen beim ersten
+ Plugin-Load auf Hellion Arctic zurück.
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
diff --git a/.github/forge-posts/v1.4.7.md b/.github/forge-posts/v1.4.7.md
index 24f62df..a413d3f 100644
--- a/.github/forge-posts/v1.4.7.md
+++ b/.github/forge-posts/v1.4.7.md
@@ -3,27 +3,31 @@ subtitle: Backlog Cleanup and Mid-Features
versionsnatur: Mid-Feature-Patch
---
-Achter Sub-Patch der v1.4.x Polish-Sweep-Serie. Erstes User-sichtbares Feature-Bundle seit v1.4.5 — angepinnte Tell-Tabs
-die Relog überleben, opt-in Honorific-Glow, plus eine konfigurierbare Sidebar.
+Achter Sub-Patch der v1.4.x Polish-Sweep-Serie. Erstes User-sichtbares Feature-Bundle seit v1.4.5 —
+angepinnte Tell-Tabs die Relog überleben, opt-in Honorific-Glow, plus eine konfigurierbare Sidebar.
-- **TempTell anpinnen**: Rechtsklick auf einen TempTell-Tab in der Sidebar → „Tab anpinnen". Angepinnte Tabs überleben
- Plugin-Reload und Char-Logout, behalten ihre Konversations-Historie (wird beim Rehydrate aus dem MessageStore
- nachgeladen) und bleiben an die gleiche /tell-Person gebunden. Hard-Cap 5 angepinnte Tabs in einem separaten Pool —
- die normalen Auto-Tell-Tabs (15er Cap) sind davon entkoppelt, Gesamt-Decke 20. Die Sidebar gruppiert angepinnte Tabs
- in einer eigenen „Angepinnt"-Sektion mit eigenem Trenner.
-- **Honorific Glow-Outline**: rendert jetzt eine 8-Richtungs-DrawList-Outline wenn der Honorific-Titel eine Glow-Farbe
- trägt. Opt-in via **Settings → Integrationen → Glow-Outline rendern (Honorific)** (Default OFF). Gradient (Color3 /
- GradientColourSet / Wave / Pulse) wird geparst und im DTO weitergereicht, rendert aktuell aber statisch als
- Primärfarbe — der volle Gradient-Port (Animations-Algorithmus + Pride-Palette) kommt als eigener Cycle nach.
-- **Sidebar-Breite konfigurierbar**: in **Theme & Layout** ein Slider 44–160 px. Default bleibt 44 px (icon-only), aber
- breiter machen damit Sektion-Header wie „Aktive Tells (3)" oder „Angepinnt (2)" nicht abgeschnitten werden.
-- **Settings-Save Channel-Fix**: ein Save mit aktivem Party- oder Linkshell-Tab konnte den Chat-Input zurück auf
- `/tell ` springen lassen. `Configuration.UpdateFrom` bewahrt jetzt den Runtime-`CurrentChannel`
- über den persistent-Tab-Merge hinweg, und `TabSwitched` deep-cloned den Seed-Channel statt sich den `UsedChannel` mit
- dem vorigen Tab zu teilen.
-- **Internal**: `IPluginLogProxy`-Indirektion vor Dalamud's `IPluginLog` über alle ~91 `Plugin.Log`-Call-Sites. Damit
- läuft `MessageStore.Migrate0` voll-isoliert in xUnit (F12.1-Lücke aus v1.4.6 geschlossen). Plus: TempTab-Counter als
- abgeleitete Property statt gecachtes Interlocked-Feld — die neuen Pin/Unpin-Übergänge sind Cold-Path, kein
- Lock-Free-Vorteil mehr. Migration v16 → v17 ist rein additiv (neues `Tab.IsPinned`-Bool, Default false).
+- **TempTell anpinnen**: Rechtsklick auf einen TempTell-Tab in der Sidebar → „Tab anpinnen".
+ Angepinnte Tabs überleben Plugin-Reload und Char-Logout, behalten ihre Konversations-Historie
+ (wird beim Rehydrate aus dem MessageStore nachgeladen) und bleiben an die gleiche /tell-Person
+ gebunden. Hard-Cap 5 angepinnte Tabs in einem separaten Pool — die normalen Auto-Tell-Tabs (15er
+ Cap) sind davon entkoppelt, Gesamt-Decke 20. Die Sidebar gruppiert angepinnte Tabs in einer
+ eigenen „Angepinnt"-Sektion mit eigenem Trenner.
+- **Honorific Glow-Outline**: rendert jetzt eine 8-Richtungs-DrawList-Outline wenn der
+ Honorific-Titel eine Glow-Farbe trägt. Opt-in via **Settings → Integrationen → Glow-Outline
+ rendern (Honorific)** (Default OFF). Gradient (Color3 / GradientColourSet / Wave / Pulse) wird
+ geparst und im DTO weitergereicht, rendert aktuell aber statisch als Primärfarbe — der volle
+ Gradient-Port (Animations-Algorithmus + Pride-Palette) kommt als eigener Cycle nach.
+- **Sidebar-Breite konfigurierbar**: in **Theme & Layout** ein Slider 44–160 px. Default bleibt 44
+ px (icon-only), aber breiter machen damit Sektion-Header wie „Aktive Tells (3)" oder „Angepinnt
+ (2)" nicht abgeschnitten werden.
+- **Settings-Save Channel-Fix**: ein Save mit aktivem Party- oder Linkshell-Tab konnte den
+ Chat-Input zurück auf `/tell ` springen lassen. `Configuration.UpdateFrom`
+ bewahrt jetzt den Runtime-`CurrentChannel` über den persistent-Tab-Merge hinweg, und `TabSwitched`
+ deep-cloned den Seed-Channel statt sich den `UsedChannel` mit dem vorigen Tab zu teilen.
+- **Internal**: `IPluginLogProxy`-Indirektion vor Dalamud's `IPluginLog` über alle ~91
+ `Plugin.Log`-Call-Sites. Damit läuft `MessageStore.Migrate0` voll-isoliert in xUnit (F12.1-Lücke
+ aus v1.4.6 geschlossen). Plus: TempTab-Counter als abgeleitete Property statt gecachtes
+ Interlocked-Feld — die neuen Pin/Unpin-Übergänge sind Cold-Path, kein Lock-Free-Vorteil mehr.
+ Migration v16 → v17 ist rein additiv (neues `Tab.IsPinned`-Bool, Default false).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
diff --git a/.github/forge-posts/v1.4.8.md b/.github/forge-posts/v1.4.8.md
index 1c2e487..84a44fa 100644
--- a/.github/forge-posts/v1.4.8.md
+++ b/.github/forge-posts/v1.4.8.md
@@ -3,19 +3,17 @@ subtitle: Hook-Layer und Polish-Quick-Wins
versionsnatur: Polish-Patch
---
-- DbViewer Volltext-Suche: optionaler FTS5-Index über die ganze Chat-Historie.
- Wird beim ersten v1.4.8-Start asynchron im Hintergrund gebaut, Progress als
- Toast. Lokale Page-Suche bleibt Default. Such-Eingaben werden als exakte
- Wortfolge gematcht; mehrere Wörter werden nur gefunden, wenn sie zusammen
- und in der Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt
+- DbViewer Volltext-Suche: optionaler FTS5-Index über die ganze Chat-Historie. Wird beim ersten
+ v1.4.8-Start asynchron im Hintergrund gebaut, Progress als Toast. Lokale Page-Suche bleibt
+ Default. Such-Eingaben werden als exakte Wortfolge gematcht; mehrere Wörter werden nur gefunden,
+ wenn sie zusammen und in der Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt
eigene Anführungszeichen um den Suchbegriff.
-- Custom-Theme-Files laden sich beim Speichern automatisch neu, wenn das Theme
- aktiv ist. Kein Picker-Klick mehr nötig.
-- Retention-Sweep blockt nicht mehr den Framework-Thread. Der Mini-Hitch von
- ~194ms pro Sweep ist weg.
+- Custom-Theme-Files laden sich beim Speichern automatisch neu, wenn das Theme aktiv ist. Kein
+ Picker-Klick mehr nötig.
+- Retention-Sweep blockt nicht mehr den Framework-Thread. Der Mini-Hitch von ~194ms pro Sweep ist
+ weg.
- Statusleiste rendert sauber bei Windows-Skalierung über 100%.
-- Receive-Suppressed-Tells-Routing wurde in diesem Cycle untersucht und auf
- v1.5.x verschoben: wenn andere Plugins Tells via CheckMessageHandled
- unterdrücken, überspringt FFXIVs Chat-Pipeline den RaptureLogModule-Resolver
- und HellionChats Tab-Routing verliert den Tell-Partner. Der Fix liegt
+- Receive-Suppressed-Tells-Routing wurde in diesem Cycle untersucht und auf v1.5.x verschoben: wenn
+ andere Plugins Tells via CheckMessageHandled unterdrücken, überspringt FFXIVs Chat-Pipeline den
+ RaptureLogModule-Resolver und HellionChats Tab-Routing verliert den Tell-Partner. Der Fix liegt
architektonisch neben dem geplanten Ad-Block-Hook-Layer und kommt dort mit.
diff --git a/.github/forge-posts/v1.4.9.md b/.github/forge-posts/v1.4.9.md
index 5cc06c8..15d4e01 100644
--- a/.github/forge-posts/v1.4.9.md
+++ b/.github/forge-posts/v1.4.9.md
@@ -3,35 +3,26 @@ subtitle: Plugin-Load Render Polish
versionsnatur: Performance-Patch
---
-- First-Frame-HITCH unter 100 ms: der erste Render-Frame des Plugins liegt
- jetzt bei ~76 ms Median (vorher ~127 ms), die Dalamud-Warnung
- „UiBuilder(Hellion Chat) > 100ms" beim Plugin-Start ist damit weg.
- Erreicht durch das Verlagern von sechs nicht-essentiellen Render-
- Sektionen (Statusleiste, Kanalname-Chunks, Fenster-Bounds-Check,
- Hinweis-Banner, Autocomplete, Input-Preview) auf den zweiten Frame.
- Bei 60 fps sieht man die deferred-Sektionen ~17 ms später, was im
- Atlas-Build-Fenster nach einem Reload unsichtbar bleibt.
-- Slash-Commands zentral registriert: /hellion, /hellionView,
- /hellionSeString und /hellionDebugger werden jetzt im Plugin-Load zentral
- registriert statt erst beim ersten Öffnen ihres Ziel-Fensters. Heißt: die
- Befehle funktionieren ab dem ersten Tick, auch wenn das jeweilige Fenster
- nie geöffnet wurde. Der „Einstellungen"-Button im Plugin-Manager hängt am
- selben Pfad.
-- Plugin-Load-Diagnose-Logs als Tripwire: die Profiling-Logs für
- MessageStore.Connect, MessageStore.Migrate, FilterAllTabs und den
- Auto-Translate-Warmup bleiben auf Information-Level eingeschaltet. Falls
- eine zukünftige Änderung die Lade-Zeit wieder über 100 ms drückt, taucht
- der Mehrverbrauch direkt im /xllog auf, ohne dass jemand erst den
- Debug-Filter einschalten muss.
-- ChatTwo-IPC-Kompatibilitäts-Layer: HellionChat spiegelt jetzt die
- komplette ChatTwo-IPC-Surface (`GetChatInputState`,
- `ChatInputStateChanged`, `Register`, `Unregister`, `Available`,
- `Invoke`) zusätzlich zu unseren eigenen `HellionChat.*`-Gates unter
- dem `ChatTwo.*`-Namensraum. Drittseitige Integrationen die nur auf
- ChatTwo's IPC reagieren, etwa die Kontextmenü-Hooks von Artisan und
- AllaganTools, funktionieren damit weiter ohne Code-Änderung auf
- ihrer Seite. Die Conflict-Detection blockiert das parallele Laden
- von ChatTwo, daher kein Namensraum-Konflikt im Live-Betrieb.
-- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-
- Aufwand. Nach dem Update läuft das Plugin gegen die bestehende
- v17-Datenbank weiter.
+- First-Frame-HITCH unter 100 ms: der erste Render-Frame des Plugins liegt jetzt bei ~76 ms Median
+ (vorher ~127 ms), die Dalamud-Warnung „UiBuilder(Hellion Chat) > 100ms" beim Plugin-Start ist
+ damit weg. Erreicht durch das Verlagern von sechs nicht-essentiellen Render- Sektionen
+ (Statusleiste, Kanalname-Chunks, Fenster-Bounds-Check, Hinweis-Banner, Autocomplete,
+ Input-Preview) auf den zweiten Frame. Bei 60 fps sieht man die deferred-Sektionen ~17 ms später,
+ was im Atlas-Build-Fenster nach einem Reload unsichtbar bleibt.
+- Slash-Commands zentral registriert: /hellion, /hellionView, /hellionSeString und /hellionDebugger
+ werden jetzt im Plugin-Load zentral registriert statt erst beim ersten Öffnen ihres Ziel-Fensters.
+ Heißt: die Befehle funktionieren ab dem ersten Tick, auch wenn das jeweilige Fenster nie geöffnet
+ wurde. Der „Einstellungen"-Button im Plugin-Manager hängt am selben Pfad.
+- Plugin-Load-Diagnose-Logs als Tripwire: die Profiling-Logs für MessageStore.Connect,
+ MessageStore.Migrate, FilterAllTabs und den Auto-Translate-Warmup bleiben auf Information-Level
+ eingeschaltet. Falls eine zukünftige Änderung die Lade-Zeit wieder über 100 ms drückt, taucht der
+ Mehrverbrauch direkt im /xllog auf, ohne dass jemand erst den Debug-Filter einschalten muss.
+- ChatTwo-IPC-Kompatibilitäts-Layer: HellionChat spiegelt jetzt die komplette ChatTwo-IPC-Surface
+ (`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`, `Invoke`)
+ zusätzlich zu unseren eigenen `HellionChat.*`-Gates unter dem `ChatTwo.*`-Namensraum. Drittseitige
+ Integrationen die nur auf ChatTwo's IPC reagieren, etwa die Kontextmenü-Hooks von Artisan und
+ AllaganTools, funktionieren damit weiter ohne Code-Änderung auf ihrer Seite. Die
+ Conflict-Detection blockiert das parallele Laden von ChatTwo, daher kein Namensraum-Konflikt im
+ Live-Betrieb.
+- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations- Aufwand. Nach dem Update
+ läuft das Plugin gegen die bestehende v17-Datenbank weiter.
diff --git a/.github/forge-posts/v1.5.0.md b/.github/forge-posts/v1.5.0.md
index a5e1d01..a4d2451 100644
--- a/.github/forge-posts/v1.5.0.md
+++ b/.github/forge-posts/v1.5.0.md
@@ -3,35 +3,26 @@ subtitle: DI Foundation und Service-Refactor
versionsnatur: Architektur-Cycle
---
-- **Architektur-Umbau ohne User-spürbare Verhaltens-Änderung:** der
- Plugin-Bootstrap wechselt auf einen Generic-Host DI-Container
- (`Microsoft.Extensions.Hosting` + `IServiceCollection`) nach dem
- Lightless-Sync-Muster. 18 Service-Klassen wandern von einem
- statischen `Plugin.LogProxy`-Locator auf typisierte
- `ILogger`-Constructor-Injection. `DalamudLogger` brückt
- `Microsoft.Extensions.Logging` über auf Dalamuds `IPluginLog` —
- im xllog erscheinen jetzt Service-spezifische Spalten wie
- `[ MessageManager]` und `[Honori...ervice]`.
-- **Plugin.LogProxy bleibt für die acht Buckets erhalten,** die
- Constructor-Injection nicht erreicht: Static-Helper (EmoteCache,
- AutoTranslate, MemoryUtil, WrapperUtil), Dalamud-Reflektion
- (Configuration), Data-Class mit Massen-Instanziierung (Message)
- und Instanz-Klassen die nur aus Static-Methods loggen (FontManager,
- eine GameFunctions-Stelle).
-- **Performance bestätigt durch Cross-Plugin-Baseline:** HellionChat
- First-Frame-HITCH 77 ms Median, Chat 2 v1.40.2 74 ms Median — kein
- DI-Penalty gegenüber dem Upstream-Fork-Origin. Lightless und
- XIVInstantMessenger liegen bei ~7 ms weil sie ihren FontAtlas-Build
- deferren; das wird das v1.5.1-Item.
-- **User-sichtbarer Bug-Fix nebenbei:** Slash-Command-Einfügen in das
- Chat-Eingabefeld (Friend-List "/tell"-Action plus Plugin-Inserts
- von Artisan, AllaganTools und ähnlichen) ersetzt jetzt den
- vorhandenen Input, statt anzukonkatenieren. Cherry-Pick aus ChatTwo
- upstream `ee7768ac` mit Namespace-Anpassung.
-- **Foundation für die Plugin-Integrations-Wave:** v1.5.7-11
- (Context-Menu, NotificationMaster, Moodles, ExtraChat, XIVIM
- Quick-DM) werden ab jetzt strukturell handhabbar — neue Services
- sind ein `services.AddSingleton` plus ein paar Factory-Lambda-
- Zeilen, kein Plugin.cs-Anflanschen mehr.
-- Migration v17 unverändert: kein Schema-Bump, kein
- Config-Migrations-Aufwand.
+- **Architektur-Umbau ohne User-spürbare Verhaltens-Änderung:** der Plugin-Bootstrap wechselt auf
+ einen Generic-Host DI-Container (`Microsoft.Extensions.Hosting` + `IServiceCollection`) nach dem
+ Lightless-Sync-Muster. 18 Service-Klassen wandern von einem statischen `Plugin.LogProxy`-Locator
+ auf typisierte `ILogger`-Constructor-Injection. `DalamudLogger` brückt
+ `Microsoft.Extensions.Logging` über auf Dalamuds `IPluginLog` — im xllog erscheinen jetzt
+ Service-spezifische Spalten wie `[ MessageManager]` und `[Honori...ervice]`.
+- **Plugin.LogProxy bleibt für die acht Buckets erhalten,** die Constructor-Injection nicht
+ erreicht: Static-Helper (EmoteCache, AutoTranslate, MemoryUtil, WrapperUtil), Dalamud-Reflektion
+ (Configuration), Data-Class mit Massen-Instanziierung (Message) und Instanz-Klassen die nur aus
+ Static-Methods loggen (FontManager, eine GameFunctions-Stelle).
+- **Performance bestätigt durch Cross-Plugin-Baseline:** HellionChat First-Frame-HITCH 77 ms Median,
+ Chat 2 v1.40.2 74 ms Median — kein DI-Penalty gegenüber dem Upstream-Fork-Origin. Lightless und
+ XIVInstantMessenger liegen bei ~7 ms weil sie ihren FontAtlas-Build deferren; das wird das
+ v1.5.1-Item.
+- **User-sichtbarer Bug-Fix nebenbei:** Slash-Command-Einfügen in das Chat-Eingabefeld (Friend-List
+ "/tell"-Action plus Plugin-Inserts von Artisan, AllaganTools und ähnlichen) ersetzt jetzt den
+ vorhandenen Input, statt anzukonkatenieren. Cherry-Pick aus ChatTwo upstream `ee7768ac` mit
+ Namespace-Anpassung.
+- **Foundation für die Plugin-Integrations-Wave:** v1.5.7-11 (Context-Menu, NotificationMaster,
+ Moodles, ExtraChat, XIVIM Quick-DM) werden ab jetzt strukturell handhabbar — neue Services sind
+ ein `services.AddSingleton` plus ein paar Factory-Lambda- Zeilen, kein Plugin.cs-Anflanschen
+ mehr.
+- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-Aufwand.
diff --git a/.github/release-footer.md b/.github/release-footer.md
index f673ded..36d850f 100644
--- a/.github/release-footer.md
+++ b/.github/release-footer.md
@@ -2,26 +2,28 @@
## How to install
-This release is distributed via the HellionChat custom repository, not the Dalamud main plugin repo. To install:
+This release is distributed via the HellionChat custom repository, not the Dalamud main plugin repo.
+To install:
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
-2. Add the URL: `https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
+2. Add the URL:
+ `https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
## Project documents
-- [README](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/README.md) — features,
- architecture, build
-- [Privacy notice](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md) — what
- the plugin stores and sends
+- [README](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/README.md)
+ — features, architecture, build
+- [Privacy notice](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md)
+ — what the plugin stores and sends
- [Third-party notices](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/docs/THIRD_PARTY_NOTICES.md)
— dependencies and licences
-- [Security policy](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md) —
- vulnerability reporting
-- [Support](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md) — bug reports,
- questions, contact paths
+- [Security policy](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md)
+ — vulnerability reporting
+- [Support](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md)
+ — bug reports, questions, contact paths
## Licence
-[EUPL-1.2](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE). Based on
-[Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna, also EUPL-1.2.
+[EUPL-1.2](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE).
+Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna, also EUPL-1.2.
diff --git a/.gitignore b/.gitignore
index 29343b9..0749cce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,193 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
+##############################################################
##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## .gitignore – Hellion Forge / Hellion Media
+##
+## Basis: github/gitignore VisualStudio.gitignore
+## Überarbeitet: Mai 2026
+## Status: Original-Patterns vollständig erhalten,
+## neu sortiert in logische Sektionen,
+## Sicherheits- & Tooling-Sektionen ergänzt.
+##
+## Markierungen:
+## [!! OBSOLET 2026 !!] → Tool offiziell eingestellt,
+## Pattern bleibt aus Vorsicht drin.
+##
+##############################################################
-# Local development environment (HellionChat fork)
+
+# =====================================================
+# [!! KRITISCH !!] Secrets, Keys & Credentials
+# Diese Sachen dürfen NIEMALS im Repo landen!
+# =====================================================
+
+# Environment Files
.env
+.env.*
.env.bak*
.envrc
!.env.example
+!.env.sample
+
+# Private Keys & Zertifikate
+*.pem
+*.key
+*.p12
+*.pfx
+*.cer
+*.crt
+*.csr
+*.gpg
+*.asc
+
+# SSH Keys (falls jemand die ins Repo legt)
+id_rsa
+id_ed25519
+id_ecdsa
+known_hosts
+
+# Auth-/Token-Files
+auth.json
+.npmrc
+.pypirc
+secrets.json
+
+# ASP.NET / .NET App-Configs mit lokalen Secrets
+appsettings.*.local.json
+appsettings.Local.json
+local.settings.json
+
+# Memory Dumps (können Credentials im Heap enthalten!)
+*.dmp
+*.mdmp
+crash.log
+
+
+# =====================================================
+# Projekt-spezifisch (HellionChat Fork)
+# =====================================================
+
+# Lokale Entwicklungsumgebung
.vscode/
scripts/setup-dev-env.sh
-# Local test project (stays out of the published plugin repo;
-# pure-function safety net for refactor cycles)
+# Lokales Test-Projekt (bleibt aus dem Plugin-Repo raus;
+# pure-function safety net für Refactor-Cycles)
HellionChat.Tests/
+ChatTwo.Tests
+TestResults
+*.db-shm
+*.db-wal
# Packaging
pack/
-# User-specific files
+# Specs und Plan-Dateien
+/.superpowers/
+
+# Claude Code lokales Setup (nicht committed)
+/.claude/
+/CLAUDE.md
+
+# Cycle-Working-Notes (im Vault gepflegt, lokales Repo-Pad bei Bedarf)
+/docs/cycle-notes/
+
+
+# =====================================================
+# OS-spezifische Files
+# =====================================================
+
+# macOS
+.DS_Store
+.AppleDouble
+.LSOverride
+._*
+
+# Windows
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+$RECYCLE.BIN/
+
+# Linux
+.directory
+.Trash-*
+
+
+# =====================================================
+# AI / LLM Tooling (2026 era)
+# =====================================================
+
+# Cursor IDE
+.cursor/
+.cursorignore
+
+# Aider
+.aider*
+
+# Continue.dev
+.continue/
+.continuerc.json
+
+# Windsurf
+.windsurf/
+
+# Sourcegraph Cody
+.cody/
+
+# Lokale Prompt-Sammlungen / Scratch-Pads
+prompts/local/
+
+
+# =====================================================
+# Editor & IDE (neben Visual Studio)
+# =====================================================
+
+# JetBrains (IntelliJ, Rider, etc.)
+.idea/
+
+# Vim / Neovim
+*.swp
+*.swo
+*.swn
+
+# Sublime Text
+*.sublime-workspace
+*.sublime-project
+
+
+# =====================================================
+# IDE & Editor – User-spezifische Files (VS)
+# =====================================================
+
+# Visual Studio User Files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
-# User-specific files (MonoDevelop/Xamarin Studio)
+# MonoDevelop/Xamarin Studio
*.userprefs
-# Mono auto generated files
-mono_crash.*
+# Visual Studio Cache/Options Directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto-generated files
+Generated\ Files/
+
+# Local History
+.localhistory/
+
+# CodeRush personal settings
+.cr/personal
+
+
+# =====================================================
+# Build Output
+# =====================================================
-# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
@@ -47,43 +203,24 @@ bld/
[Ll]og/
[Ll]ogs/
-# Visual Studio 2015/2017 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUnit
-*.VisualState.xml
-TestResult.xml
-nunit-*.xml
-
-# Build Results of an ATL Project
+# ATL Project Build Output
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
+# MigrationBackup (Package Reference Convert Tool)
+MigrationBackup/
-# StyleCop
-StyleCopReport.xml
-# Files built by Visual Studio
+# =====================================================
+# Build-Artefakte (Files built by Visual Studio)
+# =====================================================
+
*_i.c
*_p.c
*_h.h
@@ -105,6 +242,7 @@ StyleCopReport.xml
*.tmp_proj
*_wpftmp.csproj
*.log
+*.binlog
*.vspscc
*.vssscc
.builds
@@ -112,10 +250,87 @@ StyleCopReport.xml
*.svclog
*.scc
-# Chutzpah Test files
+
+# =====================================================
+# Test Results
+# =====================================================
+
+# MSTest
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# Verify / Snapshot Testing (modern .NET, Spotty Wisdom)
+*.received.*
+*.received.txt
+
+# [!! OBSOLET 2026 !!] Chutzpah – Repository auf GitHub archiviert
_Chutzpah*
+
+# =====================================================
+# Code Coverage
+# =====================================================
+
+# Coverlet
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage
+*.coverage
+*.coveragexml
+
+# DotCover (JetBrains)
+*.dotCover
+
+# AxoCover
+.axoCover/*
+!.axoCover/settings.json
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# OpenCover UI Analysis
+OpenCover/
+
+# [!! OBSOLET 2026 !!] MightyMoose / AutoTest.Net – seit >10 Jahren nicht mehr gepflegt
+*.mm.*
+AutoTest.Net/
+
+
+# =====================================================
+# Profiler & Trace
+# =====================================================
+
+# Visual Studio Profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# NVidia Nsight GPU Debugger
+*.nvuser
+
+
+# =====================================================
+# Cache Files (VS, C++, Sass)
+# =====================================================
+
# Visual C++ cache files
+# Hinweis: Manche Patterns hier werden auch vom C#-Linter genutzt (z. B. *.lscache)
ipch/
*.aps
*.ncb
@@ -125,101 +340,80 @@ ipch/
*.cachefile
*.VC.db
*.VC.VC.opendb
+*.lscache
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
+# Visual Studio cache (.cache files allgemein, .cache directories behalten)
+*.[Cc]ache
+!?*.[Cc]ache/
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
+# Web Workbench Sass
.sass-cache/
-# Installshield output folder
-[Ee]xpress/
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
+# =====================================================
+# NuGet & Dependencies
+# =====================================================
# NuGet Packages
*.nupkg
-# NuGet Symbol Packages
*.snupkg
-# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Fody – auto-generated XML schema
+FodyWeavers.xsd
+
+# Node (falls JS-Tooling im Build genutzt wird)
+.ntvs_analysis.dat
+node_modules/
+
+# Python Tools für Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+
+# =====================================================
+# Mono
+# =====================================================
+
+mono_crash.*
+
+
+# =====================================================
+# Publish & Deploy
+# =====================================================
+
+# Click-Once
+publish/
+
+# Publish Web Output
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.[Pp]ublish.xml
+*.azurePubxml
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App Publish Settings
+# Comment the next line if you want to checkin your Azure Web App publish settings,
+# but sensitive information contained in these scripts will be unencrypted
+PublishScripts/
+
# Microsoft Azure Build Output
csx/
*.build.csdef
@@ -228,7 +422,35 @@ csx/
ecf/
rcf/
-# Windows Store app package directories and files
+# Service Fabric Backup
+ServiceFabricBackup/
+
+# Installshield
+[Ee]xpress/
+
+
+# =====================================================
+# Container / Infrastructure-as-Code (Vorsicht: Tokens!)
+# =====================================================
+
+# Docker
+docker-compose.override.yml
+
+# Terraform
+.terraform/
+*.tfstate
+*.tfstate.*
+*.tfvars
+!example.tfvars
+
+# Serverless Framework
+.serverless/
+
+
+# =====================================================
+# Windows Store / AppX
+# =====================================================
+
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
@@ -237,50 +459,29 @@ _pkginfo.txt
*.appxbundle
*.appxupload
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!?*.[Cc]ache/
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
+# =====================================================
+# Datenbanken & SQL
+# =====================================================
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
+# SQL Server
*.mdf
*.ldf
*.ndf
-# Business Intelligence projects
+# Andere DB-bezogene
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+
+# [!! OBSOLET 2026 !!] BeatPulse – wurde 2019 umbenannt zu AspNetCore.Diagnostics.HealthChecks
+healthchecksdb
+
+
+# =====================================================
+# Business Intelligence / Reporting
+# =====================================================
+
*.rdl.data
*.bim.layout
*.bim_*.settings
@@ -288,27 +489,97 @@ ServiceFabricBackup/
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
+*.rptproj.bak
+
+
+# =====================================================
+# Add-ins & Analyzer Tools
+# =====================================================
+
+# ReSharper
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity
+_TeamCity*
+
+# StyleCop
+StyleCopReport.xml
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# Guidance Automation Toolkit
+*.gpState
# Microsoft Fakes
FakesAssemblies/
-# GhostDoc plugin setting file
+# [!! OBSOLET 2026 !!] GhostDoc Plugin – Submain hat das Tool eingestellt
*.GhostDoc.xml
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
+# Tabs Studio
+*.tss
-# Visual Studio 6 build log
+# Telerik JustMock
+*.jmconfig
+
+# MFractors (Xamarin productivity tool)
+.mfractor/
+
+# DocProject Documentation Generator
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+
+# =====================================================
+# Sonstige Sprachen & Tooling
+# =====================================================
+
+# Ionide (F# VS Code Tools)
+.ionide/
+
+# Azure Stream Analytics Local Run
+ASALocalRun/
+
+# BizTalk Build Output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# Orleans
+orleans.codegen.cs
+
+
+# =====================================================
+# [!! OBSOLET 2026 !!] Legacy-Tooling (eingestellt)
+# Patterns bleiben aus Vorsicht drin.
+# =====================================================
+
+# [!! OBSOLET 2026 !!] TFS 2012 Local Workspace – ersetzt durch Azure DevOps
+$tf/
+
+# [!! OBSOLET 2026 !!] Visual Studio 6 Build Log – VS6 ist von 1998
*.plg
-# Visual Studio 6 workspace options file
+# [!! OBSOLET 2026 !!] Visual Studio 6 Workspace Options
*.opt
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+# [!! OBSOLET 2026 !!] Visual Studio 6 Workspace File
*.vbw
-# Visual Studio LightSwitch build output
+# [!! OBSOLET 2026 !!] RIA / Silverlight – Microsoft hat das Okt. 2021 eingestellt
+Generated_Code/
+
+# [!! OBSOLET 2026 !!] Visual Studio LightSwitch – von Microsoft eingestellt
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
@@ -316,75 +587,31 @@ node_modules/
**/*.Server/ModelManifest.xml
_Pvt_Extensions
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-# FAKE - F# Make
-.fake/
+# =====================================================
+# Upgrade / Backup-Reports
+# =====================================================
-# CodeRush personal settings
-.cr/personal
+# Backup-Files vom Konvertieren alter VS-Projekte (wir haben ja git ;-))
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
+# =====================================================
+# Misc / Temp / Backup
+# =====================================================
-# Tabs Studio
-*.tss
+ClientBin/
+~$*
+*~
+*.publishsettings
-# Telerik's JustMock configuration file
-*.jmconfig
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
-
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Backup folder for Package Reference Convert tool in Visual Studio 2017
-MigrationBackup/
-
-# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
-#Specs und Plan datein
-/.superpowers/
-
-#Test Datein
-ChatTwo.Tests
-TestResults
-*.db-shm
-*.db-wal
-
-# Claude Code projekt-spezifisches Setup (lokal, nicht committed)
-/.claude/
-/CLAUDE.md
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
\ No newline at end of file
diff --git a/.markdownlint.json b/.markdownlint.json
index 085bab1..6f410e1 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -1,9 +1,17 @@
{
- "MD007": { "indent": 4 },
+ "MD003": { "style": "atx" },
+ "MD004": { "style": "dash" },
+ "MD007": { "indent": 2 },
+ "MD009": { "br_spaces": 2, "strict": false, "list_item_empty_lines": false },
"MD013": false,
"MD024": { "siblings_only": true },
"MD029": false,
"MD033": false,
"MD036": false,
- "MD041": false
+ "MD040": true,
+ "MD041": false,
+ "MD046": { "style": "fenced" },
+ "MD048": { "style": "backtick" },
+ "MD049": { "style": "underscore" },
+ "MD050": { "style": "asterisk" }
}
diff --git a/.prettierignore b/.prettierignore
index 19bce32..2e4b598 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,50 @@
+# ##############################################################
+# #
+# # .prettierignore – Hellion Forge / Hellion Media
+# #
+# # Files die Prettier NICHT anfassen soll.
+# # Überarbeitet: Mai 2026
+# #
+# # Hinweis: Prettier liest auch .gitignore automatisch mit.
+# # Hier nur Sachen die zusätzlich ignoriert werden müssen
+# # oder die im Repo liegen aber nicht formatiert werden dürfen.
+# #
+# ##############################################################
+
+
+# === .NET Build Output ===
bin/
obj/
+
+# === JS / Web Build Output ===
node_modules/
-*.Designer.cs
\ No newline at end of file
+dist/
+out/
+build/
+coverage/
+
+# === Generierte C#-Files (Designer, Source Generators) ===
+*.Designer.cs
+*.g.cs
+*.g.i.cs
+*.generated.cs
+*.AssemblyInfo.cs
+*.AssemblyAttributes.cs
+
+# === Lock-Files (NIE umformatieren – zerschießt den Hash) ===
+package-lock.json
+yarn.lock
+pnpm-lock.yaml
+packages.lock.json
+
+# === Minified Files (bewusst kompakt, niemals anfassen) ===
+*.min.js
+*.min.css
+
+# === Test-Snapshots (z. B. Verify) ===
+*.received.*
+*.verified.*
+**/__snapshots__/
+
+# === Plugin-Manifest (DalamudPackager-Schema, fix lassen) ===
+HellionChat/HellionChat.yaml
diff --git a/.prettierrc.json b/.prettierrc.json
index 42bc65f..d32ff4f 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,7 +1,35 @@
{
"printWidth": 120,
"tabWidth": 4,
- "proseWrap": "always",
+ "useTabs": false,
+ "semi": true,
"singleQuote": false,
- "endOfLine": "lf"
+ "trailingComma": "all",
+ "bracketSpacing": true,
+ "arrowParens": "always",
+ "proseWrap": "always",
+ "endOfLine": "lf",
+ "overrides": [
+ {
+ "files": "*.md",
+ "options": {
+ "printWidth": 100,
+ "tabWidth": 2
+ }
+ },
+ {
+ "files": ["*.yml", "*.yaml"],
+ "options": {
+ "tabWidth": 2,
+ "singleQuote": true
+ }
+ },
+ {
+ "files": "*.json",
+ "options": {
+ "tabWidth": 4,
+ "trailingComma": "none"
+ }
+ }
+ ]
}
diff --git a/.yamllint.yaml b/.yamllint.yaml
index d93399f..a15fc67 100644
--- a/.yamllint.yaml
+++ b/.yamllint.yaml
@@ -1,8 +1,47 @@
+# ##############################################################
+# #
+# # .yamllint.yaml – Hellion Forge / Hellion Media
+# #
+# # YAML-Linting Konfiguration.
+# # Überarbeitet: Mai 2026
+# #
+# # Regel-Doku:
+# # https://yamllint.readthedocs.io/en/stable/rules.html
+# #
+# ##############################################################
+
extends: default
+
rules:
- line-length: disable
- document-start: disable
- truthy:
- allowed-values: ["true", "false", "on"]
- empty-lines:
- max: 1
+ # Zeilenlängen-Check aus (konsistent mit markdownlint MD013)
+ line-length: disable
+
+ # YAML ohne führendes "---" erlaubt
+ document-start: disable
+
+ # GitHub Actions nutzt "on:" als Trigger-Key.
+ # Ohne diesen Override würde yamllint das als boolean "on" beklagen.
+ truthy:
+ allowed-values: ['true', 'false', 'on']
+
+ # Maximal 1 Leerzeile in Folge (saubere Files)
+ empty-lines:
+ max: 1
+
+ # YAML-Standard ist 2 Spaces (auch GitHub Actions erwartet das).
+ # Explizit setzen, um Konsistenz im Repo zu erzwingen.
+ indentation:
+ spaces: 2
+ indent-sequences: true
+ check-multi-line-strings: false
+
+ # Kommentare brauchen Space nach #, müssen mit Content beginnen
+ comments:
+ require-starting-space: true
+ min-spaces-from-content: 1
+
+ # Kein Whitespace am Zeilenende
+ trailing-spaces: enable
+
+ # Datei muss mit Newline enden
+ new-line-at-end-of-file: enable
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index ea90fb5..9be9e62 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,14 +2,15 @@
## A Note on This Project
-HellionChat is a one-person side project developed under Hellion Forge. I maintain this in my spare time, which means
-replies can take a few days. Please do not escalate just because a thread is quiet.
+HellionChat is a one-person side project developed under Hellion Forge. I maintain this in my spare
+time, which means replies can take a few days. Please do not escalate just because a thread is
+quiet.
-When in doubt, assume good intent. Contributors come from different backgrounds, time zones and skill levels. A
-clarifying question is almost always a better first move than an accusation.
+When in doubt, assume good intent. Contributors come from different backgrounds, time zones and
+skill levels. A clarifying question is almost always a better first move than an accusation.
-Please also keep discussions on topic. This project is about a Dalamud chat plugin. Off-topic arguments belong
-elsewhere.
+Please also keep discussions on topic. This project is about a Dalamud chat plugin. Off-topic
+arguments belong elsewhere.
---
@@ -17,20 +18,21 @@ elsewhere.
We pledge to make our community welcoming, safe, and equitable for all.
-We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all
-individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics,
-neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or
-religion, national or social origin, socio-economic position, level of education, or other status. The same privileges
-of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
+We are committed to fostering an environment that respects and promotes the dignity, rights, and
+contributions of all individuals, regardless of characteristics including race, ethnicity, caste,
+color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or
+expression, sexual orientation, language, philosophy or religion, national or social origin,
+socio-economic position, level of education, or other status. The same privileges of participation
+are extended to everyone who participates in good faith and in accordance with this Covenant.
## Encouraged Behaviors
-While acknowledging differences in social norms, we all strive to meet our community's expectations for positive
-behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture,
-background, or native language.
+While acknowledging differences in social norms, we all strive to meet our community's expectations
+for positive behavior. We also understand that our words and actions may be interpreted differently
+than we intend based on culture, background, or native language.
-With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared
-values, including:
+With these considerations in mind, we agree to behave mindfully toward each other and act in ways
+that center our shared values, including:
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
2. Engaging **kindly and honestly** with others.
@@ -42,31 +44,32 @@ values, including:
## Restricted Behaviors
-We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are
-violations of this Code of Conduct.
+We agree to restrict the following behaviors in our community. Instances, threats, and promotion of
+these behaviors are violations of this Code of Conduct.
-1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any
- clear request to stop.
-2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of
- people.
-3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis of immutable
- identities or traits.
-4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or
- purpose of the community.
-5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their
- permission.
-6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
+1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal
+ attention after any clear request to stop.
+2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a
+ community member or group of people.
+3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis
+ of immutable identities or traits.
+4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate
+ in the context or purpose of the community.
+5. **Violating confidentiality.** Sharing or acting on someone's personal or private information
+ without their permission.
+6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person
+ or group.
7. Behaving in other ways that **threaten the well-being** of our community.
### Other Restrictions
-1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade
- enforcement actions.
+1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone
+ else to evade enforcement actions.
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
-3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the
- community.
-4. **Irresponsible communication.** Failing to responsibly present content which includes, links to, or describes any
- other restricted behaviors.
+3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside
+ the norms of the community.
+4. **Irresponsible communication.** Failing to responsibly present content which includes, links to,
+ or describes any other restricted behaviors.
## Reporting
@@ -77,12 +80,13 @@ If something here is being broken, contact me directly. Do not open a public iss
| Email | `kontakt@hellion-media.de` |
| Discord DM | `@j.j_kazama` |
-Reports stay private. I will acknowledge within a few weekdays (European business hours) and tell you what I plan to do.
+Reports stay private. I will acknowledge within a few weekdays (European business hours) and tell
+you what I plan to do.
## Enforcement
-I am the sole maintainer, so enforcement is a single-person process. I will pick the lightest measure that actually
-resolves the situation:
+I am the sole maintainer, so enforcement is a single-person process. I will pick the lightest
+measure that actually resolves the situation:
1. Private note asking the behaviour to stop.
2. Public correction in the affected thread.
@@ -95,16 +99,16 @@ Severe cases skip the lower steps. I will not negotiate over harassment or threa
## Scope
-This Code of Conduct applies to all spaces the project owns or that I run on its behalf: the GitHub repository, GitHub
-Discussions, project-related Discord conversations, and the maintainer contact listed in [`SECURITY.md`](SECURITY.md).
-It also applies when someone is identifiably representing HellionChat elsewhere, for example when posting as a
-HellionChat maintainer in the Dalamud Discord.
+This Code of Conduct applies to all spaces the project owns or that I run on its behalf: the GitHub
+repository, GitHub Discussions, project-related Discord conversations, and the maintainer contact
+listed in [`SECURITY.md`](SECURITY.md). It also applies when someone is identifiably representing
+HellionChat elsewhere, for example when posting as a HellionChat maintainer in the Dalamud Discord.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, available at
[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
-Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy
-of this license, visit
+Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA
+4.0. To view a copy of this license, visit
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8333b20..3cdf385 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,69 +1,75 @@
# Contributing to HellionChat
-Thanks for taking a look. HellionChat is a one-person side project developed under Hellion Forge. It started as a fork
-of [Chat 2](https://github.com/Infiziert90/ChatTwo) and has since become a standalone plugin under its own namespace,
-IPC channels and source tree (standalone-cut completed in v1.0.0). Forking HellionChat itself is explicitly permitted
-under the EUPL-1.2.
+Thanks for taking a look. HellionChat is a one-person side project developed under Hellion Forge. It
+started as a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) and has since become a
+standalone plugin under its own namespace, IPC channels and source tree (standalone-cut completed in
+v1.0.0). Forking HellionChat itself is explicitly permitted under the EUPL-1.2.
-This document explains what I am looking for, what I am not, and how to make a contribution land smoothly.
+This document explains what I am looking for, what I am not, and how to make a contribution land
+smoothly.
## Before You Open Anything
-- Read the [README](README.md) so you understand the scope: a privacy-focused, EUPL-1.2-licensed Dalamud plugin that
- intentionally removes the upstream webinterface and ships privacy-first defaults.
-- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active cherry-picking from upstream Chat 2 has ended in the
- v1.4.x cycle; HellionChat continues as an independent codebase. Existing upstream-derived code keeps its attribution.
- New contributions stand on their own and do not need to be cherry-pick-compatible.
-- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes through a private advisory, never a public issue
- or PR.
+- Read the [README](README.md) so you understand the scope: a privacy-focused, EUPL-1.2-licensed
+ Dalamud plugin that intentionally removes the upstream webinterface and ships privacy-first
+ defaults.
+- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active cherry-picking from upstream Chat 2
+ has ended in the v1.4.x cycle; HellionChat continues as an independent codebase. Existing
+ upstream-derived code keeps its attribution. New contributions stand on their own and do not need
+ to be cherry-pick-compatible.
+- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes through a private advisory,
+ never a public issue or PR.
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
## What I Will Accept
- Bug fixes for behaviour documented in the README, the in-plugin settings or the changelog.
- Translation contributions for Hellion-specific strings via direct pull requests against
- `HellionChat/Resources/HellionStrings.*.resx`. Translations for upstream Chat 2 strings (`Language.*.resx`) are not
- handled here; those go to the upstream Chat 2 project.
+ `HellionChat/Resources/HellionStrings.*.resx`. Translations for upstream Chat 2 strings
+ (`Language.*.resx`) are not handled here; those go to the upstream Chat 2 project.
- Documentation improvements (README, comments, this file).
- Performance fixes with a measurable before/after.
-- New features that fit the privacy-first scope and do not duplicate what an existing Dalamud plugin already does well.
+- New features that fit the privacy-first scope and do not duplicate what an existing Dalamud plugin
+ already does well.
## What I Will Probably Decline
-- Re-introducing the webinterface or any remote-access feature. It was removed in v0.2.0 on purpose. See the README
- section "Was gegenüber Chat 2 fehlt".
-- Features that bypass the privacy filter or weaken the default retention behaviour without an explicit, documented
- opt-in.
-- Sweeping refactors that touch large parts of the codebase. The maintenance cost outweighs the benefit for a one-person
- project. (This used to be doubly important because of the upstream cherry-pick path; that path is closed now, but the
- rule still holds on its own merits.)
-- AI-generated code dropped in without disclosure or human review. See [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md)
- for how I handle AI assistance on my side; I expect comparable transparency from contributors.
+- Re-introducing the webinterface or any remote-access feature. It was removed in v0.2.0 on purpose.
+ See the README section "Was gegenüber Chat 2 fehlt".
+- Features that bypass the privacy filter or weaken the default retention behaviour without an
+ explicit, documented opt-in.
+- Sweeping refactors that touch large parts of the codebase. The maintenance cost outweighs the
+ benefit for a one-person project. (This used to be doubly important because of the upstream
+ cherry-pick path; that path is closed now, but the rule still holds on its own merits.)
+- AI-generated code dropped in without disclosure or human review. See
+ [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle AI assistance on my side; I
+ expect comparable transparency from contributors.
-If you are unsure whether an idea fits, open a feature-request issue first and ask before writing code. I would rather
-say "no" to a proposal than to a finished pull request.
+If you are unsure whether an idea fits, open a feature-request issue first and ask before writing
+code. I would rather say "no" to a proposal than to a finished pull request.
## Workflow
-1. Open an issue (bug or feature request) using the templates under `.github/ISSUE_TEMPLATE/`. Skip this for trivial
- typos.
-2. Fork the repository and branch off `main`. Branch naming is informal; something like `fix/auto-tell-history-empty` or
- `feat/theme-export` is fine.
-3. Match the existing code style. The repository ships an `.editorconfig` that VS Code and Rider pick up automatically.
-4. Keep commits focused. Several small commits with clear messages are easier to review than one large one.
- Squash-on-merge happens at the PR level if needed.
+1. Open an issue (bug or feature request) using the templates under `.github/ISSUE_TEMPLATE/`. Skip
+ this for trivial typos.
+2. Fork the repository and branch off `main`. Branch naming is informal; something like
+ `fix/auto-tell-history-empty` or `feat/theme-export` is fine.
+3. Match the existing code style. The repository ships an `.editorconfig` that VS Code and Rider
+ pick up automatically.
+4. Keep commits focused. Several small commits with clear messages are easier to review than one
+ large one. Squash-on-merge happens at the PR level if needed.
5. If your change touches user-visible behaviour, update the README and/or the changelog block in
`HellionChat/HellionChat.yaml` and `repo.json`. I bump the version number myself at release time.
-6. Open the pull request against `main`. The PR template will ask you to summarise the change, the testing you did and
- any compatibility notes.
+6. Open the pull request against `main`. The PR template will ask you to summarise the change, the
+ testing you did and any compatibility notes.
## Build and Test
The project targets `net10.0-windows` against Dalamud SDK 15. To build locally you need:
- .NET 10 SDK
-- A working Dalamud dev environment with `DALAMUD_HOME` set (XIVLauncher installed and launched once is the simplest
- path)
+- A working Dalamud dev environment with `DALAMUD_HOME` set (XIVLauncher installed and launched once
+ is the simplest path)
- VS Code with the C# Dev Kit, Rider, or Visual Studio
```bash
@@ -71,11 +77,12 @@ dotnet restore
dotnet build HellionChat.sln -c Release
```
-There are currently no tests in `HellionChat.sln`. If you add a test project, point it at the relevant subsystems
-(privacy filter, configuration migration, message store) and mention it in the PR.
+There are currently no tests in `HellionChat.sln`. If you add a test project, point it at the
+relevant subsystems (privacy filter, configuration migration, message store) and mention it in the
+PR.
-For a smoke test in-game: build, copy the output into your Dalamud `devPlugins/HellionChat/` directory and load it via
-`/xlplugins`.
+For a smoke test in-game: build, copy the output into your Dalamud `devPlugins/HellionChat/`
+directory and load it via `/xlplugins`.
## Continuous Integration
@@ -86,30 +93,33 @@ Every push and every pull request runs:
| `build.yml` | `dotnet build` and `dotnet test` |
| `codeql.yml` | CodeQL security analysis |
-A pull request will not be merged while either of these is failing. CodeQL findings on changed code need to be
-addressed; pre-existing findings on untouched code are tracked separately.
+A pull request will not be merged while either of these is failing. CodeQL findings on changed code
+need to be addressed; pre-existing findings on untouched code are tracked separately.
## Translations
Hellion-specific strings live in `HellionChat/Resources/HellionStrings.resx` (English source) and
`HellionStrings..resx` (per-language). These are accepted as direct pull requests.
-The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx` are **not** translated here. They are kept as-is
-from the last upstream sync and remain the work of the Chat 2 Crowdin community. Active cherry-picking from upstream
-ended in the v1.4.x cycle (see [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future translation improvements to
-those upstream strings will not flow into HellionChat automatically anymore. If you have improvements for the original
-Chat 2 strings, please contribute them to [Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) directly.
+The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx` are **not** translated here.
+They are kept as-is from the last upstream sync and remain the work of the Chat 2 Crowdin community.
+Active cherry-picking from upstream ended in the v1.4.x cycle (see
+[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future translation improvements to those
+upstream strings will not flow into HellionChat automatically anymore. If you have improvements for
+the original Chat 2 strings, please contribute them to
+[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) directly.
## Licensing
By submitting a pull request you confirm that:
-- Your contribution is your own work, or you have the right to contribute it under the project licence.
-- You agree that your contribution will be released under the [EUPL-1.2](LICENSE), the same licence as the rest of the
- project.
+- Your contribution is your own work, or you have the right to contribute it under the project
+ licence.
+- You agree that your contribution will be released under the [EUPL-1.2](LICENSE), the same licence
+ as the rest of the project.
-There is no separate CLA. Forking HellionChat is explicitly permitted under the EUPL-1.2, as with any EUPL-licensed
-project.
+There is no separate CLA. Forking HellionChat is explicitly permitted under the EUPL-1.2, as with
+any EUPL-licensed project.
## Response Times
@@ -119,8 +129,9 @@ project.
| Discord DM | `@j.j_kazama` |
| Email | `kontakt@hellion-media.de` |
-I respond on weekdays during European business hours and take weekends and FFXIV patch days off. A pull request that
-sits for a few days has not been ignored. Pinging once after a week is fine; please do not ping daily.
+I respond on weekdays during European business hours and take weekends and FFXIV patch days off. A
+pull request that sits for a few days has not been ignored. Pinging once after a week is fine;
+please do not ping daily.
## First-time setup
@@ -130,9 +141,10 @@ After cloning, run once:
./scripts/setup-hooks.sh
```
-This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight (versions/manifest/changelog/build).
+This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight
+(versions/manifest/changelog/build).
### Test suite
-The plugin's test suite lives in a separate local repository and is not part of this codebase. If you need access for
-development, contact the maintainer.
+The plugin's test suite lives in a separate local repository and is not part of this codebase. If
+you need access for development, contact the maintainer.
diff --git a/NOTICE.md b/NOTICE.md
index ea66bcf..9b38b46 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -3,82 +3,90 @@
## Acknowledgements
HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) by
-**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and **[Anna](https://github.com/anna-is-cute)**, both of whom
-kept that plugin running and maintained for years before I ever opened the source. Without their work this fork would
-not exist, full stop. I owe them the architecture, the message store, the channel filtering, the sidebar tab system, the
-hooks into FFXIV's chat, the localisation infrastructure, and countless small decisions that I only noticed because they
-had already been made correctly.
+**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
+**[Anna](https://github.com/anna-is-cute)**, both of whom kept that plugin running and maintained
+for years before I ever opened the source. Without their work this fork would not exist, full stop.
+I owe them the architecture, the message store, the channel filtering, the sidebar tab system, the
+hooks into FFXIV's chat, the localisation infrastructure, and countless small decisions that I only
+noticed because they had already been made correctly.
-If you find HellionChat useful, please remember that the foundation came from Chat 2. The code Anna and Infi wrote is
-doing most of the heavy lifting in this fork too.
+If you find HellionChat useful, please remember that the foundation came from Chat 2. The code Anna
+and Infi wrote is doing most of the heavy lifting in this fork too.
## A direct word to Infi and Anna
-Hi. I am Florian. I forked Chat 2 because I wanted a privacy-by-default version for my own use case and a small group of
-friends I play with, not because I thought I could do anything better than what you built. The opposite is true.
-ChatTwo's default of full history and cross-character logging is the right call for most users. I just have a different
-threat model and a different data-handling philosophy that fits a smaller, locally-stored, retention- limited approach.
+Hi. I am Florian. I forked Chat 2 because I wanted a privacy-by-default version for my own use case
+and a small group of friends I play with, not because I thought I could do anything better than what
+you built. The opposite is true. ChatTwo's default of full history and cross-character logging is
+the right call for most users. I just have a different threat model and a different data-handling
+philosophy that fits a smaller, locally-stored, retention- limited approach.
-What HellionChat adds is mostly Hellion-specific surface area: a privacy filter, per-channel retention windows, an
-export pipeline, an Auto-Tell- Tabs feature for FFXIV club greeters, the Hellion theme and font, German localisation,
-and a settings UX rebuild. None of it touches the bones of what you built. Where I had to modify your code I tried to
-keep the edits minimal, isolated to clearly-marked Hellion files, and reversible.
+What HellionChat adds is mostly Hellion-specific surface area: a privacy filter, per-channel
+retention windows, an export pipeline, an Auto-Tell- Tabs feature for FFXIV club greeters, the
+Hellion theme and font, German localisation, and a settings UX rebuild. None of it touches the bones
+of what you built. Where I had to modify your code I tried to keep the edits minimal, isolated to
+clearly-marked Hellion files, and reversible.
-Concrete example: when API 15 hit, I cherry-picked your fix for the BetterTTV emote regression with `git cherry-pick -x`
-so authorship and co-author trail stay intact. That was the standard I held to as long as cherry-picking was viable, and
-you should never have to look at this fork and wonder if I quietly ate your work.
+Concrete example: when API 15 hit, I cherry-picked your fix for the BetterTTV emote regression with
+`git cherry-pick -x` so authorship and co-author trail stay intact. That was the standard I held to
+as long as cherry-picking was viable, and you should never have to look at this fork and wonder if I
+quietly ate your work.
With ChatTwo entering its rework cycle, the active cherry-pick pipeline is closed since v1.4.x — see
-[docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md) for the full reasoning. The attribution standard stays exactly the same:
-every existing `(cherry picked from commit ...)` line remains in the git history, the EUPL-1.2 anchor lines in source
-files are untouched, and this NOTICE.md remains canonical. If anything from this point forward originates from Chat 2 it
-will be a hand-port at most, called out as such in the commit message and source comments, not a `git cherry-pick`.
+[docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md) for the full reasoning. The attribution standard
+stays exactly the same: every existing `(cherry picked from commit ...)` line remains in the git
+history, the EUPL-1.2 anchor lines in source files are untouched, and this NOTICE.md remains
+canonical. If anything from this point forward originates from Chat 2 it will be a hand-port at
+most, called out as such in the commit message and source comments, not a `git cherry-pick`.
-If anything in this fork ever steps on something you would not be okay with, please reach out and I will fix it.
-Genuinely. The list of contacts is below.
+If anything in this fork ever steps on something you would not be okay with, please reach out and I
+will fix it. Genuinely. The list of contacts is below.
## Maintainer contact
-If something in HellionChat causes problems, especially if it relates back to Chat 2 or to anything Infi or Anna would
-want flagged:
+If something in HellionChat causes problems, especially if it relates back to Chat 2 or to anything
+Infi or Anna would want flagged:
- **Gitea Issues:**
[JonKazama-Hellion/HellionChat/issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
- **Discord:** `@j.j_kazama`
- **Email (business):**
-I respond on weekdays during European business hours. For anything urgent (security, attribution, takedown), email is
-the fastest path.
+I respond on weekdays during European business hours. For anything urgent (security, attribution,
+takedown), email is the fastest path.
## Why this fork is not upstreamed
-The privacy-by-default position fits a small audience. ChatTwo's full-history-by-default position fits a much larger
-one, including the roleplaying community where chat archive is part of the play experience. Trying to upstream
-HellionChat's defaults would have meant arguing that Chat 2's defaults are wrong, and they are not. They are right for
-the user base ChatTwo serves. So I keep the fork separate and attribute clearly. Active cherry-picking from upstream
-stopped in the v1.4.x cycle once Chat 2's rework made selective patches no longer portable; the existing cherry-pick
-trail stays in the git history.
+The privacy-by-default position fits a small audience. ChatTwo's full-history-by-default position
+fits a much larger one, including the roleplaying community where chat archive is part of the play
+experience. Trying to upstream HellionChat's defaults would have meant arguing that Chat 2's
+defaults are wrong, and they are not. They are right for the user base ChatTwo serves. So I keep the
+fork separate and attribute clearly. Active cherry-picking from upstream stopped in the v1.4.x cycle
+once Chat 2's rework made selective patches no longer portable; the existing cherry-pick trail stays
+in the git history.
## Why HellionChat left the GitHub fork network
-The Dalamud plugin ecosystem treats the GitHub-Fork relation as a signal that a fork is either a development branch or a
-dead mirror. HellionChat is neither. It is an independently-maintained EUPL-1.2 fork with its own release cadence, its
-own custom repo, its own user base. Detaching the fork-network relation just makes the situation honest. The git
-history, the existing cherry-pick trail, and the attribution stay exactly the same. The only thing that changes is the
-GitHub UI no longer says "forked from".
+The Dalamud plugin ecosystem treats the GitHub-Fork relation as a signal that a fork is either a
+development branch or a dead mirror. HellionChat is neither. It is an independently-maintained
+EUPL-1.2 fork with its own release cadence, its own custom repo, its own user base. Detaching the
+fork-network relation just makes the situation honest. The git history, the existing cherry-pick
+trail, and the attribution stay exactly the same. The only thing that changes is the GitHub UI no
+longer says "forked from".
## Trademarks and naming
-"Chat 2" and "ChatTwo" are the names Infi and Anna chose for the upstream plugin. HellionChat does not use either of
-those names in user-facing copy except where required to describe origin (settings tab, manifest, this file, the
-README). The Hellion brand is mine.
+"Chat 2" and "ChatTwo" are the names Infi and Anna chose for the upstream plugin. HellionChat does
+not use either of those names in user-facing copy except where required to describe origin (settings
+tab, manifest, this file, the README). The Hellion brand is mine.
## Questions
-This file is the canonical place for "is this attribution correct, is the maintainer reachable, is the relationship to
-Chat 2 documented". If anything in here is wrong, please open an issue or contact me directly.
+This file is the canonical place for "is this attribution correct, is the maintainer reachable, is
+the relationship to Chat 2 documented". If anything in here is wrong, please open an issue or
+contact me directly.
---
-Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
-[hellion-media.de](https://hellion-media.de)
+Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
+Harzburg | [hellion-media.de](https://hellion-media.de)
diff --git a/PRIVACY.md b/PRIVACY.md
index c2b047d..214186f 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -1,12 +1,14 @@
# Privacy notice
-HellionChat is a Dalamud plugin for FINAL FANTASY XIV, focused on giving the user explicit control over what their chat
-client stores locally. This document describes what the plugin does with your data, what it does not do, and how you
-exercise the rights the GDPR gives you over data you generate yourself.
+HellionChat is a Dalamud plugin for FINAL FANTASY XIV, focused on giving the user explicit control
+over what their chat client stores locally. This document describes what the plugin does with your
+data, what it does not do, and how you exercise the rights the GDPR gives you over data you generate
+yourself.
-This document is informational. The maintainer of HellionChat is **not** a controller or processor of your data in the
-GDPR sense, because no data ever leaves your machine on the maintainer's infrastructure. Independently of that, the
-plugin is built so that you can act on your own data the way the GDPR expects.
+This document is informational. The maintainer of HellionChat is **not** a controller or processor
+of your data in the GDPR sense, because no data ever leaves your machine on the maintainer's
+infrastructure. Independently of that, the plugin is built so that you can act on your own data the
+way the GDPR expects.
Last reviewed: 2026-05-05 (HellionChat v1.1.0).
@@ -14,34 +16,37 @@ Last reviewed: 2026-05-05 (HellionChat v1.1.0).
## TL;DR
-- All chat data the plugin stores stays on your machine, in your Dalamud `pluginConfigs/HellionChat/` directory.
-- The plugin does not phone home. No telemetry, no analytics, no crash reporter, no usage counter, no remote update
- check beyond what Dalamud itself does.
-- One outbound network call exists by design: the BetterTTV emote service (for chat emotes). It is documented in detail
- below and can be reasoned about per request.
-- You can export every message the plugin has stored, in Markdown, JSON or CSV, and you can wipe stored history per
- channel, per date range, or globally.
+- All chat data the plugin stores stays on your machine, in your Dalamud
+ `pluginConfigs/HellionChat/` directory.
+- The plugin does not phone home. No telemetry, no analytics, no crash reporter, no usage counter,
+ no remote update check beyond what Dalamud itself does.
+- One outbound network call exists by design: the BetterTTV emote service (for chat emotes). It is
+ documented in detail below and can be reasoned about per request.
+- You can export every message the plugin has stored, in Markdown, JSON or CSV, and you can wipe
+ stored history per channel, per date range, or globally.
---
## What the plugin stores locally
-HellionChat keeps three kinds of state on your machine, all under `%appdata%\XIVLauncher\pluginConfigs\HellionChat\` on
-Windows (`~/.xlcore/pluginConfigs/HellionChat/` on Linux/macOS via XIVLauncher Core):
+HellionChat keeps three kinds of state on your machine, all under
+`%appdata%\XIVLauncher\pluginConfigs\HellionChat\` on Windows
+(`~/.xlcore/pluginConfigs/HellionChat/` on Linux/macOS via XIVLauncher Core):
-1. **Configuration** (`HellionChat.json`). Plugin settings, channel whitelist, retention values, layout state, theme
- colours. Contains no chat content.
+1. **Configuration** (`HellionChat.json`). Plugin settings, channel whitelist, retention values,
+ layout state, theme colours. Contains no chat content.
-2. **Message database** (SQLite file in the same directory). Chat messages from the channels on your whitelist, stored
- as MessagePack-encoded blobs. The default whitelist out of the box covers only your own conversations: tells, party,
- free company, linkshells, cross-world linkshells, alliance, ExtraChat. Public chat, NPC dialogue, system messages and
- battle logs are dropped on the storage layer and never written to disk.
+2. **Message database** (SQLite file in the same directory). Chat messages from the channels on your
+ whitelist, stored as MessagePack-encoded blobs. The default whitelist out of the box covers only
+ your own conversations: tells, party, free company, linkshells, cross-world linkshells, alliance,
+ ExtraChat. Public chat, NPC dialogue, system messages and battle logs are dropped on the storage
+ layer and never written to disk.
-3. **Cached emote images** (`EmoteCacheV1/` directory). Image files downloaded from BetterTTV when an emote appears in a
- message you receive. See "Outbound network calls" below.
+3. **Cached emote images** (`EmoteCacheV1/` directory). Image files downloaded from BetterTTV when
+ an emote appears in a message you receive. See "Outbound network calls" below.
-There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/HellionChat/` is independent from
-`pluginConfigs/ChatTwo/`.
+There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/HellionChat/` is
+independent from `pluginConfigs/ChatTwo/`.
### Retention defaults
@@ -49,44 +54,49 @@ There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/Hellion
- Your-conversation channels (party, FC, linkshells, cross-world LS, alliance, ExtraChat): 90 days
- Global default for anything else: 30 days
-**Retention is off by default.** The plugin does not delete anything on its own until you explicitly turn the retention
-sweep on in the settings. Until then, stored messages stay until you clear them.
+**Retention is off by default.** The plugin does not delete anything on its own until you explicitly
+turn the retention sweep on in the settings. Until then, stored messages stay until you clear them.
---
## What the plugin does not store
-- Public chat (`/say`, `/yell`, `/shout`), NPC dialogue, system messages and battle logs. These are filtered before they
- reach the storage layer.
-- Anything from channels you remove from the whitelist. The privacy filter runs on the way in, not on the way out.
-- Login credentials, character IDs, account IDs. The plugin uses whatever Dalamud already exposes about the local
- character to attribute messages. Nothing of that is sent anywhere or persisted beyond the message itself.
+- Public chat (`/say`, `/yell`, `/shout`), NPC dialogue, system messages and battle logs. These are
+ filtered before they reach the storage layer.
+- Anything from channels you remove from the whitelist. The privacy filter runs on the way in, not
+ on the way out.
+- Login credentials, character IDs, account IDs. The plugin uses whatever Dalamud already exposes
+ about the local character to attribute messages. Nothing of that is sent anywhere or persisted
+ beyond the message itself.
---
## Outbound network calls
-HellionChat makes two kinds of automatic outbound network requests. Both are inherited from upstream Chat 2 and both are
-documented here because "GDPR-by-design" means you should know what your client does on your behalf.
+HellionChat makes two kinds of automatic outbound network requests. Both are inherited from upstream
+Chat 2 and both are documented here because "GDPR-by-design" means you should know what your client
+does on your behalf.
### 1. BetterTTV emote service (`api.betterttv.net`, `cdn.betterttv.net`)
-- **What it does:** When a chat message arrives that references a BetterTTV emote, the plugin asks the BetterTTV API for
- the emote metadata and downloads the image from the BetterTTV CDN to display it inline.
-- **What is sent:** A standard HTTPS GET request. Your IP address reaches BetterTTV (unavoidable for any HTTPS request);
- the request itself contains no identifying user data, no character name, no message text. Only the emote ID being
- looked up is in the URL path.
+- **What it does:** When a chat message arrives that references a BetterTTV emote, the plugin asks
+ the BetterTTV API for the emote metadata and downloads the image from the BetterTTV CDN to display
+ it inline.
+- **What is sent:** A standard HTTPS GET request. Your IP address reaches BetterTTV (unavoidable for
+ any HTTPS request); the request itself contains no identifying user data, no character name, no
+ message text. Only the emote ID being looked up is in the URL path.
- **When it triggers:**
- - The emote _list_ (global emotes plus the top-1500 community emotes over fifteen API pages) is fetched from
- `api.betterttv.net` once per session at plugin startup, provided the **Show emotes** option is on. This first
- list-fetch happens before any chat message has arrived. BetterTTV's edge therefore sees your IP as soon as the
- plugin loads, not only after an emote is mentioned.
- - The individual emote _images_ on `cdn.betterttv.net` are fetched on demand, only when an incoming chat message
- contains a token matching one of the cached IDs. These are cached locally (`emoteCache/`) and reused across
- sessions.
+ - The emote _list_ (global emotes plus the top-1500 community emotes over fifteen API pages) is
+ fetched from `api.betterttv.net` once per session at plugin startup, provided the **Show
+ emotes** option is on. This first list-fetch happens before any chat message has arrived.
+ BetterTTV's edge therefore sees your IP as soon as the plugin loads, not only after an emote is
+ mentioned.
+ - The individual emote _images_ on `cdn.betterttv.net` are fetched on demand, only when an
+ incoming chat message contains a token matching one of the cached IDs. These are cached locally
+ (`emoteCache/`) and reused across sessions.
- **Cached:** Yes, in `emoteCache/`. A given emote is downloaded once per machine and reused.
-- **How to opt out:** Turn off the **Show emotes** option in Settings → Chat. With it disabled, the emote cache does not
- load and no requests to BetterTTV are made for the rest of the session.
+- **How to opt out:** Turn off the **Show emotes** option in Settings → Chat. With it disabled, the
+ emote cache does not load and no requests to BetterTTV are made for the rest of the session.
- **BetterTTV's privacy policy:**
Source: `HellionChat/EmoteCache.cs`.
@@ -94,69 +104,73 @@ Source: `HellionChat/EmoteCache.cs`.
### 2. Square Enix Lodestone font (removed in v1.0.4)
Earlier versions of HellionChat (and upstream Chat 2) downloaded `FFXIV_Lodestone_SSF.ttf` from
-`img.finalfantasyxiv.com` once during font setup. That code path was a leftover from upstream's removed webinterface
-feature and was no longer consumed anywhere. The in-game symbol glyphs (job icons, item glyphs, status effects) come
-from Dalamud's bundled symbol-font helper, not from the downloaded TTF.
+`img.finalfantasyxiv.com` once during font setup. That code path was a leftover from upstream's
+removed webinterface feature and was no longer consumed anywhere. The in-game symbol glyphs (job
+icons, item glyphs, status effects) come from Dalamud's bundled symbol-font helper, not from the
+downloaded TTF.
-The download was removed in v1.0.4. As of that version HellionChat makes no automatic network call to Square Enix or to
-any `finalfantasyxiv.com` host.
+The download was removed in v1.0.4. As of that version HellionChat makes no automatic network call
+to Square Enix or to any `finalfantasyxiv.com` host.
-Cached `FFXIV_Lodestone_SSF.ttf` files left over from earlier versions remain in `pluginConfigs/HellionChat/` until
-manually deleted. They are no longer read.
+Cached `FFXIV_Lodestone_SSF.ttf` files left over from earlier versions remain in
+`pluginConfigs/HellionChat/` until manually deleted. They are no longer read.
### Links you click yourself (no automatic traffic)
-The settings panel contains a few buttons that open external pages in your browser when you click them: the upstream
-Chat 2 GitHub repo, the upstream maintainers' Ko-fi pages, the HellionChat issue tracker and `hellion-media.de`. Nothing
-happens until you click. They are documented here for completeness, not because they generate background traffic.
+The settings panel contains a few buttons that open external pages in your browser when you click
+them: the upstream Chat 2 GitHub repo, the upstream maintainers' Ko-fi pages, the HellionChat issue
+tracker and `hellion-media.de`. Nothing happens until you click. They are documented here for
+completeness, not because they generate background traffic.
---
## What the plugin does not do
-- **No telemetry.** Source verified: no calls to AppInsights, Sentry, PostHog, Plausible, Google Analytics, Microsoft
- Clarity or any comparable service exist in the codebase, nor in the direct dependencies the plugin pulls in. See
- `docs/THIRD_PARTY_NOTICES.md`.
-- **No crash reporting.** Crashes go to Dalamud's local `xllog`, not to a remote endpoint controlled by HellionChat.
-- **No usage counters.** The plugin does not count installs, sessions, feature usage, channel activity or anything else
- for the maintainer.
-- **No phone-home update check.** Updates are delivered through Dalamud's plugin installer, which polls the custom-repo
- `repo.json` on GitHub. That is GitHub's traffic and falls under GitHub's privacy policy. The plugin code does no
- separate update check.
-- **No background sync.** Messages stay on your machine. No cloud backup, no sharing feature, no remote viewer.
+- **No telemetry.** Source verified: no calls to AppInsights, Sentry, PostHog, Plausible, Google
+ Analytics, Microsoft Clarity or any comparable service exist in the codebase, nor in the direct
+ dependencies the plugin pulls in. See `docs/THIRD_PARTY_NOTICES.md`.
+- **No crash reporting.** Crashes go to Dalamud's local `xllog`, not to a remote endpoint controlled
+ by HellionChat.
+- **No usage counters.** The plugin does not count installs, sessions, feature usage, channel
+ activity or anything else for the maintainer.
+- **No phone-home update check.** Updates are delivered through Dalamud's plugin installer, which
+ polls the custom-repo `repo.json` on GitHub. That is GitHub's traffic and falls under GitHub's
+ privacy policy. The plugin code does no separate update check.
+- **No background sync.** Messages stay on your machine. No cloud backup, no sharing feature, no
+ remote viewer.
---
## Your data, your rights
-The GDPR gives you specific rights over data about you. Because HellionChat stores everything locally, those rights
-translate directly into plugin features:
+The GDPR gives you specific rights over data about you. Because HellionChat stores everything
+locally, those rights translate directly into plugin features:
### Right to access (Art. 15)
-Use the export feature in the plugin settings. You can export to **Markdown**, **JSON** or **CSV**, filtered by channel,
-date range or sender substring. The export goes through a Dalamud file dialog and writes wherever you point it, on your
-machine.
+Use the export feature in the plugin settings. You can export to **Markdown**, **JSON** or **CSV**,
+filtered by channel, date range or sender substring. The export goes through a Dalamud file dialog
+and writes wherever you point it, on your machine.
### Right to erasure (Art. 17)
Two options:
-1. **Targeted deletion.** The "retroactive cleanup" feature lets you apply your current whitelist to the existing
- database. It shows a preview of what will be removed before you confirm with Ctrl+Shift, runs in the background, and
- calls `VACUUM` afterwards to actually shrink the file.
-2. **Full deletion.** Close the game and delete the `pluginConfigs/HellionChat/` directory. The next plugin start will
- produce a fresh, empty configuration.
+1. **Targeted deletion.** The "retroactive cleanup" feature lets you apply your current whitelist to
+ the existing database. It shows a preview of what will be removed before you confirm with
+ Ctrl+Shift, runs in the background, and calls `VACUUM` afterwards to actually shrink the file.
+2. **Full deletion.** Close the game and delete the `pluginConfigs/HellionChat/` directory. The next
+ plugin start will produce a fresh, empty configuration.
### Right to portability (Art. 20)
-The JSON and CSV exports are open formats. The Markdown export is human-readable and machine-parseable. Nothing is
-locked into a proprietary container.
+The JSON and CSV exports are open formats. The Markdown export is human-readable and
+machine-parseable. Nothing is locked into a proprietary container.
### Right to object / restrict processing (Art. 21, 18)
-Adjust the channel whitelist or set retention to a low value. Both take effect immediately on new messages. Existing
-data needs the retroactive cleanup to apply retroactively, by design.
+Adjust the channel whitelist or set retention to a low value. Both take effect immediately on new
+messages. Existing data needs the retroactive cleanup to apply retroactively, by design.
---
@@ -168,32 +182,34 @@ data needs the retroactive cleanup to apply retroactively, by design.
| Hellion Forge (Gitea, self-hosted by Hellion Online Media) | Plugin distribution via custom repo, issue tracker | Whatever the Gitea instance sees from any HTTPS request to a public repo | |
| Dalamud / XIVLauncher (goatcorp) | Plugin loader, font subsystem, repo polling | Whatever Dalamud reports for itself; out of HellionChat's scope | |
-The Hellion Forge Gitea instance and the Dalamud/XIVLauncher loader are unavoidable for anyone using HellionChat through
-Dalamud at all. BetterTTV is the only third party HellionChat introduces on top of that baseline, and it is opt-out via
-settings.
+The Hellion Forge Gitea instance and the Dalamud/XIVLauncher loader are unavoidable for anyone using
+HellionChat through Dalamud at all. BetterTTV is the only third party HellionChat introduces on top
+of that baseline, and it is opt-out via settings.
---
## Dependencies that touch the network
-For a full dependency inventory see `docs/THIRD_PARTY_NOTICES.md`. Of the direct dependencies the plugin pulls in:
+For a full dependency inventory see `docs/THIRD_PARTY_NOTICES.md`. Of the direct dependencies the
+plugin pulls in:
- `MessagePack`: local serialisation, no network.
- `Microsoft.Data.Sqlite`: local SQLite access, no network.
- `morelinq`: LINQ helpers, no network.
- `Pidgin`: parser combinators, no network.
-- `SixLabors.ImageSharp`: image decoding (used for the BetterTTV emote pipeline), no network on its own.
+- `SixLabors.ImageSharp`: image decoding (used for the BetterTTV emote pipeline), no network on its
+ own.
-The single network call listed under "Outbound network calls" is written directly in HellionChat's own source, not
-delegated to a dependency.
+The single network call listed under "Outbound network calls" is written directly in HellionChat's
+own source, not delegated to a dependency.
---
## Changes to this notice
-If a future release changes what HellionChat stores, sends or caches, this document will be updated and the change
-called out in the changelog block of that release. The "Last reviewed" date at the top tracks the version this document
-is accurate for.
+If a future release changes what HellionChat stores, sends or caches, this document will be updated
+and the change called out in the changelog block of that release. The "Last reviewed" date at the
+top tracks the version this document is accurate for.
---
@@ -204,10 +220,10 @@ For privacy-related questions specific to HellionChat:
- Email: `kontakt@hellion-media.de`
- Discord DM: `@j.j_kazama`
-Security-relevant findings (for example, the plugin storing or sending something this document says it does not) go
-through the private advisory in `SECURITY.md`, not a public issue.
+Security-relevant findings (for example, the plugin storing or sending something this document says
+it does not) go through the private advisory in `SECURITY.md`, not a public issue.
---
-Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
-[hellion-media.de](https://hellion-media.de)
+Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
+Harzburg | [hellion-media.de](https://hellion-media.de)
diff --git a/README.md b/README.md
index bc444fe..b1c0193 100644
--- a/README.md
+++ b/README.md
@@ -14,27 +14,30 @@
**Version 1.5.0** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
-Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine comes from Chat 2
-(message store, channel logic, hook system), and most keyboard shortcuts continue to work as you'd expect. What changes:
-stricter privacy defaults out of the box, custom slash commands under `/hellionchat`, no web interface, and as of
-v1.1.0, a theme engine as a step toward a distinct UI look and feel.
+Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine
+comes from Chat 2 (message store, channel logic, hook system), and most keyboard shortcuts continue
+to work as you'd expect. What changes: stricter privacy defaults out of the box, custom slash
+commands under `/hellionchat`, no web interface, and as of v1.1.0, a theme engine as a step toward a
+distinct UI look and feel.
-The data-handling focus is on GDPR/EU, US, and JP regulations, as far as practically applicable for a chat plugin:
-per-channel retention periods, granular filters, and self-service data export. A full breakdown is available in
-[`PRIVACY.md`](PRIVACY.md).
+The data-handling focus is on GDPR/EU, US, and JP regulations, as far as practically applicable for
+a chat plugin: per-channel retention periods, granular filters, and self-service data export. A full
+breakdown is available in [`PRIVACY.md`](PRIVACY.md).
-This is a standalone repository, licensed under EUPL-1.2. With v1.0.0 the standalone cut is complete: own namespace
-`HellionChat.*`, own IPC channels, own source tree structure. Distribution via custom repo. Active upstream sync ended
-with the v1.4.x cycle: Chat 2 is undergoing a fundamental rework and cherry-picks are no longer portable. From here,
-Hellion Chat continues as an independent codebase — background and attribution in
+This is a standalone repository, licensed under EUPL-1.2. With v1.0.0 the standalone cut is
+complete: own namespace `HellionChat.*`, own IPC channels, own source tree structure. Distribution
+via custom repo. Active upstream sync ended with the v1.4.x cycle: Chat 2 is undergoing a
+fundamental rework and cherry-picks are no longer portable. From here, Hellion Chat continues as an
+independent codebase — background and attribution in
[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
## Acknowledgements
Hellion Chat is built on [Chat 2](https://github.com/Infiziert90/ChatTwo) by
-**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and **[Anna](https://github.com/anna-is-cute)**, who maintained
-the plugin for years before I ever saw the source code. The entire core architecture, the message store, the channel
-logic, the hook system, and much more all come from them. If Hellion Chat helps you, the credit for that belongs in
+**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
+**[Anna](https://github.com/anna-is-cute)**, who maintained the plugin for years before I ever saw
+the source code. The entire core architecture, the message store, the channel logic, the hook
+system, and much more all come from them. If Hellion Chat helps you, the credit for that belongs in
large part to Infi and Anna. A full acknowledgement is in [NOTICE.md](NOTICE.md).
Hellion Chat is developed under **Hellion Forge**, the specialized modding and plugin branch of
@@ -62,67 +65,73 @@ Hellion Chat is developed under **Hellion Forge**, the specialized modding and p
### Privacy / Compliance
-- **Channel whitelist** for database persistence with privacy-first defaults. Out of the box, only your own
- conversations are stored (tells, party, FC, linkshells, cross-world linkshells, alliance, ExtraChat). Public chat, NPC
- dialogue, system spam, and battle logs are discarded at the storage layer.
-- **Per-channel retention periods** with a daily background cleanup. Tells: 365 days, own conversation channels: 90
- days, global default: 30 days. The default is OFF — the plugin deletes nothing without explicit consent.
-- **Retroactive cleanup** with preview and Ctrl+Shift confirmation. Applies the current whitelist to an existing
- database, runs in the background, and calls VACUUM afterward.
-- **Export** to Markdown, JSON, or CSV via the Dalamud file dialog (GDPR Art. 15 right of access). Filter by channel,
- date range, or sender substring.
+- **Channel whitelist** for database persistence with privacy-first defaults. Out of the box, only
+ your own conversations are stored (tells, party, FC, linkshells, cross-world linkshells, alliance,
+ ExtraChat). Public chat, NPC dialogue, system spam, and battle logs are discarded at the storage
+ layer.
+- **Per-channel retention periods** with a daily background cleanup. Tells: 365 days, own
+ conversation channels: 90 days, global default: 30 days. The default is OFF — the plugin deletes
+ nothing without explicit consent.
+- **Retroactive cleanup** with preview and Ctrl+Shift confirmation. Applies the current whitelist to
+ an existing database, runs in the background, and calls VACUUM afterward.
+- **Export** to Markdown, JSON, or CSV via the Dalamud file dialog (GDPR Art. 15 right of access).
+ Filter by channel, date range, or sender substring.
- **Full privacy overview** in [`PRIVACY.md`](PRIVACY.md) and third-party components in
- [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md): what is stored, which two outbound calls exist
- (BetterTTV opt-out, Square Enix Lodestone font), an explicit no-telemetry statement, and the mapping of GDPR rights
- (Art. 15/17/18/20/21) to concrete plugin functions.
+ [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md): what is stored, which two outbound
+ calls exist (BetterTTV opt-out, Square Enix Lodestone font), an explicit no-telemetry statement,
+ and the mapping of GDPR rights (Art. 15/17/18/20/21) to concrete plugin functions.
### Onboarding
-- **First-run wizard** with three profiles (Privacy-First, Relaxed, Full History) and a GDPR notice for the "Full
- History" option.
-- **Configuration migration** seeds privacy defaults for existing users and shows a notification on the first plugin
- start after an update. With v1.0.0, users on config version 12 or older also receive a one-time tab layout reset; the
- old tab configuration is backed up as `pluginConfigs/HellionChat.json.pre-v13-backup`.
-- **Layout migration from Chat 2** moves configuration and database to `pluginConfigs/HellionChat/` without data loss.
- Handles locked files gracefully (warns the user if Chat 2 is still loaded).
+- **First-run wizard** with three profiles (Privacy-First, Relaxed, Full History) and a GDPR notice
+ for the "Full History" option.
+- **Configuration migration** seeds privacy defaults for existing users and shows a notification on
+ the first plugin start after an update. With v1.0.0, users on config version 12 or older also
+ receive a one-time tab layout reset; the old tab configuration is backed up as
+ `pluginConfigs/HellionChat.json.pre-v13-backup`.
+- **Layout migration from Chat 2** moves configuration and database to `pluginConfigs/HellionChat/`
+ without data loss. Handles locked files gracefully (warns the user if Chat 2 is still loaded).
- **Migrate3 recovery** heals partially migrated databases from old Chat 2 installations.
### Look & Feel
-- **Bilingual UI** (English and German) with live language switching. Hellion-specific strings are in
- `HellionStrings..resx`.
-- **Hellion HUD theme** with cyan-teal accents, slate-violet tabs, and amber highlights for active states.
-- **Chat color presets** (v0.6.0) with seven built-in bundles in Settings → Appearance → Chat Colors: Classic (Chat 2
- default), High Contrast, Pastel, Dark Mode Tuned, Hellion (brand), plus bonus moods Night Blue and Indigo Violet.
- One-click apply, battle channels remain untouched.
+- **Bilingual UI** (English and German) with live language switching. Hellion-specific strings are
+ in `HellionStrings..resx`.
+- **Hellion HUD theme** with cyan-teal accents, slate-violet tabs, and amber highlights for active
+ states.
+- **Chat color presets** (v0.6.0) with seven built-in bundles in Settings → Appearance → Chat
+ Colors: Classic (Chat 2 default), High Contrast, Pastel, Dark Mode Tuned, Hellion (brand), plus
+ bonus moods Night Blue and Indigo Violet. One-click apply, battle channels remain untouched.
- **Window opacity slider** for combat-friendly transparency.
- **Bundled Hellion font** (Exo 2, OFL-1.1) as an optional default instead of the system font.
- **Hellion logo** bundled in the plugin and displayed in the Dalamud plugin list.
#### Custom Themes (v1.1.0)
-HellionChat ships a theme engine with ten built-in themes (Hellion Arctic, Hellion Spectrum, Chat 2 Classic, Event
-Horizon, Crystal Nocturne, Mint Grove, Night Blue, Indigo Violet, Forge Merchantman, Synthwave Sunset) and a JSON-based
-authoring format for custom themes. Schema and step-by-step guide in
-[`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md). Hellion Spectrum is Deuteranopia/Protanopia-safe (red-green color
-blindness) based on the Wong/Okabe-Ito palette.
+HellionChat ships a theme engine with ten built-in themes (Hellion Arctic, Hellion Spectrum, Chat 2
+Classic, Event Horizon, Crystal Nocturne, Mint Grove, Night Blue, Indigo Violet, Forge Merchantman,
+Synthwave Sunset) and a JSON-based authoring format for custom themes. Schema and step-by-step guide
+in [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md). Hellion Spectrum is
+Deuteranopia/Protanopia-safe (red-green color blindness) based on the Wong/Okabe-Ito palette.
#### Plugin Integrations (v1.3.0)
-- **Honorific custom titles in the chat header.** When the Honorific plugin is active and a custom title is set, it is
- displayed in the chat header above the message log. Auto-detect with silent fallback: without Honorific the slot is
- invisible. Toggle in Settings → Integrations → Honorific. First cycle of a multi-stage plugin integration roadmap
- (context menu, NotificationMaster, RP status, ExtraChat, and XIVIM to follow).
+- **Honorific custom titles in the chat header.** When the Honorific plugin is active and a custom
+ title is set, it is displayed in the chat header above the message log. Auto-detect with silent
+ fallback: without Honorific the slot is invisible. Toggle in Settings → Integrations → Honorific.
+ First cycle of a multi-stage plugin integration roadmap (context menu, NotificationMaster, RP
+ status, ExtraChat, and XIVIM to follow).
### Pop-Out Convenience (v0.6.0)
-- **Input bar in pop-out windows** as a global opt-in in Settings → Windows → Window Frame. When active, every pop-out
- window has a compact input at the bottom with a channel-colored icon button and text field. No more switching back to
- the main window for a quick reply.
-- **Per-pop-out independent text buffer and history cursor.** Changing channels in a pop-out works globally like in the
- main window (FFXIV channel API), but half-typed input doesn't collide between the main window and pop-outs.
-- **Shared input history** across all windows via singleton service. Up/Down arrow keys navigate the same list of the
- last 30 entries everywhere.
+- **Input bar in pop-out windows** as a global opt-in in Settings → Windows → Window Frame. When
+ active, every pop-out window has a compact input at the bottom with a channel-colored icon button
+ and text field. No more switching back to the main window for a quick reply.
+- **Per-pop-out independent text buffer and history cursor.** Changing channels in a pop-out works
+ globally like in the main window (FFXIV channel API), but half-typed input doesn't collide between
+ the main window and pop-outs.
+- **Shared input history** across all windows via singleton service. Up/Down arrow keys navigate the
+ same list of the last 30 entries everywhere.
### Stability
@@ -132,12 +141,13 @@ blindness) based on the Wong/Okabe-Ito palette.
### What's missing compared to Chat 2
-The web interface was removed in Hellion Chat 0.2.0. It serves a different use case than the focus of this fork — remote
-access to chat from a second device — which conflicts directly with the privacy-first premise: a chat plugin that starts
-a local HTTP server is too large an attack surface for my threat model. So it's gone.
+The web interface was removed in Hellion Chat 0.2.0. It serves a different use case than the focus
+of this fork — remote access to chat from a second device — which conflicts directly with the
+privacy-first premise: a chat plugin that starts a local HTTP server is too large an attack surface
+for my threat model. So it's gone.
-If you want the full Chat 2 feature set, the upstream plugin serves you better. Hellion Chat is deliberately the slimmer
-fork.
+If you want the full Chat 2 feature set, the upstream plugin serves you better. Hellion Chat is
+deliberately the slimmer fork.
---
@@ -172,24 +182,26 @@ HellionChat/
### Rules
-- **Code namespace is `HellionChat.*`.** Fully consolidated onto the plugin name since v1.0.0 — no remaining `ChatTwo.*`
- anywhere in the source tree.
-- **AssemblyName is `HellionChat`.** Own slot in `pluginConfigs/`, own file manifest, no shared state with Chat 2.
- Parallel-loading upstream Chat 2 is actively blocked on startup (bilingual conflict message).
-- **IPC channels are `HellionChat.*`.** Six channels for third-party plugin integration (`Register`, `Available`,
- `Unregister`, `Invoke`, `GetChatInputState`, `ChatInputStateChanged`). Details in [`docs/IPC.md`](docs/IPC.md).
-- **Hellion-specific strings in `HellionStrings.*.resx`**, strings carried over from Chat 2 in `Language.*.resx`. The
- original `Language.*.resx` structure is preserved because the existing translations from the upstream Crowdin
- community remain valuable.
-- **No direct access to `Plugin.Interface.UiBuilder.FontAtlas`** outside of `FontManager`. Font fallback and the Hellion
- font are managed centrally.
+- **Code namespace is `HellionChat.*`.** Fully consolidated onto the plugin name since v1.0.0 — no
+ remaining `ChatTwo.*` anywhere in the source tree.
+- **AssemblyName is `HellionChat`.** Own slot in `pluginConfigs/`, own file manifest, no shared
+ state with Chat 2. Parallel-loading upstream Chat 2 is actively blocked on startup (bilingual
+ conflict message).
+- **IPC channels are `HellionChat.*`.** Six channels for third-party plugin integration (`Register`,
+ `Available`, `Unregister`, `Invoke`, `GetChatInputState`, `ChatInputStateChanged`). Details in
+ [`docs/IPC.md`](docs/IPC.md).
+- **Hellion-specific strings in `HellionStrings.*.resx`**, strings carried over from Chat 2 in
+ `Language.*.resx`. The original `Language.*.resx` structure is preserved because the existing
+ translations from the upstream Crowdin community remain valuable.
+- **No direct access to `Plugin.Interface.UiBuilder.FontAtlas`** outside of `FontManager`. Font
+ fallback and the Hellion font are managed centrally.
---
## Database
-SQLite, schema inherited from upstream Chat 2 (migration level v3). Hellion extensions live in `Configuration` as
-fields, not in the DB schema:
+SQLite, schema inherited from upstream Chat 2 (migration level v3). Hellion extensions live in
+`Configuration` as fields, not in the DB schema:
| Column | Type | Description |
| ---------------- | ------- | ---------------------------- |
@@ -229,15 +241,15 @@ Hellion Chat is distributed via a Dalamud **custom repository**.
### Migration from Chat 2 (with existing history)
-Chat 2 and Hellion Chat share the database file until Hellion Chat moves it to its own path on first start. Order
-matters:
+Chat 2 and Hellion Chat share the database file until Hellion Chat moves it to its own path on first
+start. Order matters:
1. **Disable Chat 2** in `/xlplugins` (do not uninstall, just disable).
2. **Fully close FFXIV** so SQLite releases the file lock. A plugin reload alone is not enough.
3. Restart the game.
4. Add the custom repo as described above.
-5. Install Hellion Chat. On first start, the configuration file and the entire database directory are moved into the
- HellionChat layout.
+5. Install Hellion Chat. On first start, the configuration file and the entire database directory
+ are moved into the HellionChat layout.
6. **Verify** under Settings → Privacy → Refresh preview that the message count looks plausible.
### Troubleshooting
@@ -267,17 +279,18 @@ Start the game, enable Hellion Chat, and your history is back.
### Updates
-Updates appear automatically in the plugin list once a new `vX.Y.Z` tag with a GitHub Release is published. No reinstall
-needed.
+Updates appear automatically in the plugin list once a new `vX.Y.Z` tag with a GitHub Release is
+published. No reinstall needed.
---
## Distribution
-Hellion Chat is distributed via its own Dalamud custom repository (`repo.json` in the repo root). Pushing a `vX.Y.Z` tag
-triggers the [`release.yml`](.github/workflows/release.yml) workflow, which attaches the build output
-(`HellionChat/bin/Release/HellionChat/latest.zip`) along with the matching changelog block from `HellionChat.yaml` to
-the GitHub Release. Manual recovery path if the auto-trigger is missed: `gh workflow run release.yml -f tag=vX.Y.Z`.
+Hellion Chat is distributed via its own Dalamud custom repository (`repo.json` in the repo root).
+Pushing a `vX.Y.Z` tag triggers the [`release.yml`](.github/workflows/release.yml) workflow, which
+attaches the build output (`HellionChat/bin/Release/HellionChat/latest.zip`) along with the matching
+changelog block from `HellionChat.yaml` to the GitHub Release. Manual recovery path if the
+auto-trigger is missed: `gh workflow run release.yml -f tag=vX.Y.Z`.
An optional submission to the Dalamud main plugin repo (in addition to the custom repo) is on the
[roadmap](docs/ROADMAP.md).
@@ -286,19 +299,22 @@ An optional submission to the Dalamud main plugin repo (in addition to the custo
## Project Status
-**Version 1.5.0** — DI Foundation and Service Refactor. Major architecture cycle: the plugin bootstrap moves to a
-generic-host DI container (`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync. All
-18 instance-class services migrate from a static `Plugin.LogProxy` locator to `Microsoft.Extensions.Logging.ILogger`
-via constructor injection, with a custom `DalamudLogger` bridging the framework over to Dalamud's `IPluginLog`. The
-proxy stays for the eight buckets ctor-injection cannot reach (static helpers like `EmoteCache`, Dalamud-reflected
-`Configuration`, the `Message` data class, and static methods inside `FontManager` / `GameFunctions`). Plugin.cs
-finishes the cycle at 1012 lines — virtually identical to the pre-cycle 1013 — because the new Phase-1 host build
-and Plugin.X bridge wiring trade out exactly the service and window allocations that left `LoadAsync`. Cross-plugin
-baseline confirms no performance penalty vs Chat 2: HellionChat first-frame HITCH 77 ms median, Chat 2 74 ms.
-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. One user-visible fix bundled in from upstream: pasting a slash command
-into the chat input (Friend List "/tell" action, plugin-driven inserts) now replaces the existing input instead of
-concatenating onto whatever the user was typing. Migration v17 stays (no schema bump).
+**Version 1.5.0** — DI Foundation and Service Refactor. Major architecture cycle: the plugin
+bootstrap moves to a generic-host DI container (`Microsoft.Extensions.Hosting` +
+`IServiceCollection`) modelled on Lightless Sync. All 18 instance-class services migrate from a
+static `Plugin.LogProxy` locator to `Microsoft.Extensions.Logging.ILogger` via constructor
+injection, with a custom `DalamudLogger` bridging the framework over to Dalamud's `IPluginLog`. The
+proxy stays for the eight buckets ctor-injection cannot reach (static helpers like `EmoteCache`,
+Dalamud-reflected `Configuration`, the `Message` data class, and static methods inside `FontManager`
+/ `GameFunctions`). Plugin.cs finishes the cycle at 1012 lines — virtually identical to the
+pre-cycle 1013 — because the new Phase-1 host build and Plugin.X bridge wiring trade out exactly the
+service and window allocations that left `LoadAsync`. Cross-plugin baseline confirms no performance
+penalty vs Chat 2: HellionChat first-frame HITCH 77 ms median, Chat 2 74 ms. 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. One user-visible fix bundled in from upstream: pasting a
+slash command into the chat input (Friend List "/tell" action, plugin-driven inserts) now replaces
+the existing input instead of concatenating onto whatever the user was typing. Migration v17 stays
+(no schema bump).
Hellion Chat is a standalone plugin, no longer a fork in the repository sense. Fully completed:
@@ -313,29 +329,31 @@ Hellion Chat is a standalone plugin, no longer a fork in the repository sense. F
- Audit hardening (path traversal, retention race, DbViewer consistency)
- About tab in Hellion branding, localized EN and DE, with license and disclaimer
- AI disclosure documented (see [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md))
-- Standalone cut: namespace `HellionChat.*`, IPC channels `HellionChat.*`, source tree restructure, conflict detection
- against upstream Chat 2, SQLite CVE hardening (3.50.3)
-- Theme engine with ten built-in themes plus JSON authoring format (engine v1.1.0, catalog extended in v1.2.3, including
- CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
-- ABGR cache on theme records: `HellionStyle.PushGlobal` reads pre-computed ABGR instead of converting RGBA→ABGR per
- slot per frame (v1.4.1, ~13% render-time recovery)
+- Standalone cut: namespace `HellionChat.*`, IPC channels `HellionChat.*`, source tree restructure,
+ conflict detection against upstream Chat 2, SQLite CVE hardening (3.50.3)
+- Theme engine with ten built-in themes plus JSON authoring format (engine v1.1.0, catalog extended
+ in v1.2.3, including CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
+- ABGR cache on theme records: `HellionStyle.PushGlobal` reads pre-computed ABGR instead of
+ converting RGBA→ABGR per slot per frame (v1.4.1, ~13% render-time recovery)
-In progress: incremental modernization of UI look and feel beyond the theme engine. What's planned next and what's on
-the long-term list is in [`docs/ROADMAP.md`](docs/ROADMAP.md). Concrete scheduled items are also tracked in the
-[Gitea issue tracker](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) under the `roadmap` label.
+In progress: incremental modernization of UI look and feel beyond the theme engine. What's planned
+next and what's on the long-term list is in [`docs/ROADMAP.md`](docs/ROADMAP.md). Concrete scheduled
+items are also tracked in the
+[Gitea issue tracker](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) under
+the `roadmap` label.
### On Release Cadence
-Anyone looking at the repo for the first time will notice a lot of releases and a high commit count in a short time.
-Both are deliberate. The full reasoning — four factors behind it — is in
+Anyone looking at the repo for the first time will notice a lot of releases and a high commit count
+in a short time. Both are deliberate. The full reasoning — four factors behind it — is in
[`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md), section "How I release this fast".
---
## Community & Support
-- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and tools):
- [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
+- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and
+ tools): [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
- Bug reports and feature requests:
[Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
- Discord DM: `@j.j_kazama`
@@ -345,25 +363,26 @@ Both are deliberate. The full reasoning — four factors behind it — is in
## License
-EUPL-1.2 (same license as upstream Chat 2). Full text in [LICENSE](LICENSE), copyright notes with dual-holder block in
-[COPYRIGHT](COPYRIGHT), personal acknowledgement to the upstream authors in [NOTICE.md](NOTICE.md).
+EUPL-1.2 (same license as upstream Chat 2). Full text in [LICENSE](LICENSE), copyright notes with
+dual-holder block in [COPYRIGHT](COPYRIGHT), personal acknowledgement to the upstream authors in
+[NOTICE.md](NOTICE.md).
-© 2023–2026 the Chat 2 authors ([Infi](https://github.com/Infiziert90), [Anna](https://github.com/anna-is-cute), and
-upstream contributors) for the engine, IPC, and storage layer. © 2026 Hellion Online Media for the Hellion Chat
-extensions.
+© 2023–2026 the Chat 2 authors ([Infi](https://github.com/Infiziert90),
+[Anna](https://github.com/anna-is-cute), and upstream contributors) for the engine, IPC, and storage
+layer. © 2026 Hellion Online Media for the Hellion Chat extensions.
### Acknowledgments
-- **[Infi](https://github.com/Infiziert90) and [Anna](https://github.com/anna-is-cute) (ascclemens)** for the Chat 2
- engine, without which this fork would not exist.
+- **[Infi](https://github.com/Infiziert90) and [Anna](https://github.com/anna-is-cute)
+ (ascclemens)** for the Chat 2 engine, without which this fork would not exist.
- **Dalamud team** for the plugin framework.
-- **Chat 2 Crowdin community** for the upstream string translations (see Settings → Info → "Chat 2 community
- translators").
+- **Chat 2 Crowdin community** for the upstream string translations (see Settings → Info → "Chat 2
+ community translators").
### FFXIV Disclaimer
-FINAL FANTASY XIV © SQUARE ENIX CO., LTD. All rights reserved. Hellion Chat is an unofficial, fan-made plugin and is not
-affiliated with, supported by, sponsored by, or approved by Square Enix.
+FINAL FANTASY XIV © SQUARE ENIX CO., LTD. All rights reserved. Hellion Chat is an unofficial,
+fan-made plugin and is not affiliated with, supported by, sponsored by, or approved by Square Enix.
### AI Assistance
@@ -373,7 +392,8 @@ See [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for the pair-level disclosu
## Project Documents
-Standard repository documents live in the repo root; deeper documentation lives under [`docs/`](docs/).
+Standard repository documents live in the repo root; deeper documentation lives under
+[`docs/`](docs/).
### Repo Root
@@ -404,5 +424,5 @@ Standard repository documents live in the repo root; deeper documentation lives
---
-Developed under **Hellion Forge**, the modding and plugin branch of **Hellion Online Media** | Bad Harzburg |
-[hellion-media.de](https://hellion-media.de)
+Developed under **Hellion Forge**, the modding and plugin branch of **Hellion Online Media** | Bad
+Harzburg | [hellion-media.de](https://hellion-media.de)
diff --git a/SECURITY.md b/SECURITY.md
index d0d28bc..cdd9438 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,8 +2,8 @@
## Reporting a Vulnerability
-If you find a security issue in HellionChat, please do not open a public Gitea issue. Use one of the private channels
-below so I can investigate and ship a fix before the details go public.
+If you find a security issue in HellionChat, please do not open a public Gitea issue. Use one of the
+private channels below so I can investigate and ship a fix before the details go public.
**Preferred:**
@@ -20,7 +20,8 @@ I respond on weekdays during European business hours.
### In scope
-- Code paths that touch user-controlled input (chat messages, plugin config, file paths the user can influence)
+- Code paths that touch user-controlled input (chat messages, plugin config, file paths the user can
+ influence)
- The privacy filter in `MessageStore.cs` and the export pipeline
- The configuration migration logic
- The `EmoteCache` HTTP client and path handling
@@ -36,10 +37,10 @@ I respond on weekdays during European business hours.
## Disclosure Window
-I aim to ship a fix within 14 days for high-severity issues and within 30 days for everything else. If a fix needs more
-time I will say so in the private thread.
+I aim to ship a fix within 14 days for high-severity issues and within 30 days for everything else.
+If a fix needs more time I will say so in the private thread.
## Credits
-Everyone who reports a real issue gets listed in the changelog of the release that fixes it, unless they prefer to stay
-anonymous. No bug bounty, nothing financial — this is a hobby plugin.
+Everyone who reports a real issue gets listed in the changelog of the release that fixes it, unless
+they prefer to stay anonymous. No bug bounty, nothing financial — this is a hobby plugin.
diff --git a/SUPPORT.md b/SUPPORT.md
index 9594c78..6cf19b0 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -1,7 +1,7 @@
# Support
-HellionChat is a small hobby project maintained by one person. There are a few different paths depending on what you
-need. Pick the one that matches.
+HellionChat is a small hobby project maintained by one person. There are a few different paths
+depending on what you need. Pick the one that matches.
## Bugs and feature requests
@@ -10,45 +10,46 @@ Gitea issues, using the templates:
- [Bug report](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=bug_report.yml)
- [Feature request](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=feature_request.yml)
-Please search [existing issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues?type=issue)
+Please search
+[existing issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues?type=issue)
first. Duplicates get closed and pointed at the original.
## Security
-Do **not** open a public issue for security-relevant findings. Use the private advisory route described in
-[SECURITY.md](SECURITY.md):
+Do **not** open a public issue for security-relevant findings. Use the private advisory route
+described in [SECURITY.md](SECURITY.md):
- Email `kontakt@hellion-media.de` (preferred for security reports)
- Discord DM `@j.j_kazama` for time-sensitive findings
## Privacy questions
-Specific questions about what HellionChat does or does not store and send are covered in [PRIVACY.md](PRIVACY.md). For
-follow-ups beyond that document:
+Specific questions about what HellionChat does or does not store and send are covered in
+[PRIVACY.md](PRIVACY.md). For follow-ups beyond that document:
- Email `kontakt@hellion-media.de`
## Quick questions and casual feedback
-- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and tools):
- [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
+- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and
+ tools): [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
- Discord DM: `@j.j_kazama`
-Bug reports still go through the issue tracker so they can be tracked, but a quick "is this a bug or am I holding it
-wrong" message is fine.
+Bug reports still go through the issue tracker so they can be tracked, but a quick "is this a bug or
+am I holding it wrong" message is fine.
## Upstream Chat 2 issues
If the issue exists in upstream Chat 2 too, please report it at
-[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo/issues). That keeps the original maintainers in the loop
-and helps everyone who uses Chat 2 directly.
+[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo/issues). That keeps the original
+maintainers in the loop and helps everyone who uses Chat 2 directly.
## Response times
-Weekdays during European business hours. On weekends and FFXIV patch days, replies will be slower. A few days of silence
-on a non-urgent issue is normal. Pinging once after a week is fine.
+Weekdays during European business hours. On weekends and FFXIV patch days, replies will be slower. A
+few days of silence on a non-urgent issue is normal. Pinging once after a week is fine.
---
-Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
-[hellion-media.de](https://hellion-media.de)
+Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
+Harzburg | [hellion-media.de](https://hellion-media.de)
diff --git a/docs/AI_DISCLOSURE.md b/docs/AI_DISCLOSURE.md
index ede1c17..ea2ccad 100644
--- a/docs/AI_DISCLOSURE.md
+++ b/docs/AI_DISCLOSURE.md
@@ -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:
+Questions about this disclosure:
+
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 074d10d..2a8b2db 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -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` 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`
+via constructor injection, bridged over Dalamud's `IPluginLog` by a custom `DalamudLogger` trio.
### Under the hood
-- 18 instance-class services migrate to `ILogger` 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` 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) 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) 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** (44–160 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 ` 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** (44–160 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 ` 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 —
@@ -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 —
@@ -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`, 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`, 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 —
@@ -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 —
@@ -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 —
@@ -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 2–6 %, real ~10–15 %)
-- `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 2–6 %, real ~10–15 %)
+- `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 —
@@ -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 —
@@ -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.
diff --git a/docs/CONTRIBUTORS.md b/docs/CONTRIBUTORS.md
index daea5a3..6268316 100644
--- a/docs/CONTRIBUTORS.md
+++ b/docs/CONTRIBUTORS.md
@@ -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..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).
diff --git a/docs/IPC.md b/docs/IPC.md
index fc7e3ca..c851b8f 100755
--- a/docs/IPC.md
+++ b/docs/IPC.md
@@ -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.
diff --git a/docs/LEARNING-JOURNEY.md b/docs/LEARNING-JOURNEY.md
index ada7b53..f0a0993 100644
--- a/docs/LEARNING-JOURNEY.md
+++ b/docs/LEARNING-JOURNEY.md
@@ -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.
---
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 44e3041..d03fa94 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -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` 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` 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 ` 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 ` 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 2–6 % was conservative; real result ~10–15 %). 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 2–6 % was conservative; real result ~10–15
+%). 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 2–6)** — 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 2–6)** — 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). 10–20 % 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). 10–20 % 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).
diff --git a/docs/THEME-AUTHORING.md b/docs/THEME-AUTHORING.md
index 5520b67..e6da235 100644
--- a/docs/THEME-AUTHORING.md
+++ b/docs/THEME-AUTHORING.md
@@ -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 `-` is recommended for theme families. The first member of a family is the
-lightest/brightest:
+Naming convention `-` 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.
---
diff --git a/docs/THIRD_PARTY_NOTICES.md b/docs/THIRD_PARTY_NOTICES.md
index 45dfe39..0cbefd8 100644
--- a/docs/THIRD_PARTY_NOTICES.md
+++ b/docs/THIRD_PARTY_NOTICES.md
@@ -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.
diff --git a/docs/UPSTREAM_SYNC.md b/docs/UPSTREAM_SYNC.md
index 7fd0fb9..74a921f 100644
--- a/docs/UPSTREAM_SYNC.md
+++ b/docs/UPSTREAM_SYNC.md
@@ -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.
diff --git a/dotnet-tools.json b/dotnet-tools.json
index 7fc4412..5573da1 100644
--- a/dotnet-tools.json
+++ b/dotnet-tools.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://json.schemastore.org/dotnet-tools.json",
"version": 1,
"isRoot": true,
"tools": {
diff --git a/scripts/preflight.sh b/scripts/preflight.sh
index 280858f..1eb618f 100755
--- a/scripts/preflight.sh
+++ b/scripts/preflight.sh
@@ -27,6 +27,6 @@ dotnet csharpier check HellionChat/
echo "==> preflight: Block F — markdownlint"
# npx --yes avoids a global install; first run caches into ~/.npm/_npx/.
# Subsequent runs are sub-second.
-npx --yes markdownlint-cli2 "**/*.md" "#node_modules" "#bin" "#obj" "#.claude"
+npx --yes markdownlint-cli2 "**/*.md" "#node_modules" "#bin" "#obj" "#.claude" "#CLAUDE.md"
echo "==> preflight: ALL GREEN"