From 0220e5d756747d93ca5f1f7c3648e80cee337325 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Sun, 17 May 2026 17:20:55 +0200 Subject: [PATCH] chore(linting): refresh configs and sweep auto-fix Pull in the refreshed linter and tooling configs (editorconfig, gitignore, gitattributes, prettierignore, prettierrc, markdownlint, yamllint, env.example, dotnet-tools) and run prettier and markdownlint in --fix / --write mode across the repo so the existing tree matches the new rules. - prettier 2-space indent on yaml/yml and json overrides, asterisk strong, underscore emphasis, proseWrap always - markdownlint MD007 indent aligned to 2 and MD049 to underscore so prettier output stays passing - preflight Block F also ignores CLAUDE.md (gitignored personal file) - prettierignore extended to keep HellionChat.yaml manifest and the NuGet packages.lock.json out of the formatter No semantic content changed; csharpier, build, full build-suite (729/729) and the new prettier/markdownlint/yamllint checks all green. --- .editorconfig | 389 ++++++----- .env.example | 63 +- .gitattributes | 184 ++++- .gitea/workflows/build.yml | 54 +- .gitea/workflows/forge-announce.yml | 430 ++++++------ .gitea/workflows/release.yml | 284 ++++---- .gitea/workflows/security.yml | 32 +- .github/ISSUE_TEMPLATE/bug_report.yml | 132 ++-- .github/ISSUE_TEMPLATE/config.yml | 20 +- .github/ISSUE_TEMPLATE/feature_request.yml | 98 ++- .github/PULL_REQUEST_TEMPLATE.md | 10 +- .github/dependabot.yml | 76 +-- .github/forge-posts/v1.1.0.md | 19 +- .github/forge-posts/v1.2.0.md | 26 +- .github/forge-posts/v1.2.1.md | 42 +- .github/forge-posts/v1.2.3.md | 32 +- .github/forge-posts/v1.3.0.md | 26 +- .github/forge-posts/v1.4.0.md | 27 +- .github/forge-posts/v1.4.1.md | 38 +- .github/forge-posts/v1.4.10.md | 49 +- .github/forge-posts/v1.4.2.md | 40 +- .github/forge-posts/v1.4.3.md | 44 +- .github/forge-posts/v1.4.4.md | 53 +- .github/forge-posts/v1.4.5.md | 43 +- .github/forge-posts/v1.4.6.md | 51 +- .github/forge-posts/v1.4.7.md | 46 +- .github/forge-posts/v1.4.8.md | 24 +- .github/forge-posts/v1.4.9.md | 55 +- .github/forge-posts/v1.5.0.md | 55 +- .github/release-footer.md | 26 +- .gitignore | 685 ++++++++++++------- .markdownlint.json | 12 +- .prettierignore | 48 +- .prettierrc.json | 32 +- .yamllint.yaml | 51 +- CODE_OF_CONDUCT.md | 92 +-- CONTRIBUTING.md | 128 ++-- NOTICE.md | 100 +-- PRIVACY.md | 208 +++--- README.md | 266 ++++---- SECURITY.md | 15 +- SUPPORT.md | 35 +- docs/AI_DISCLOSURE.md | 44 +- docs/CHANGELOG.md | 749 +++++++++++---------- docs/CONTRIBUTORS.md | 84 ++- docs/IPC.md | 85 +-- docs/LEARNING-JOURNEY.md | 348 +++++----- docs/ROADMAP.md | 440 ++++++------ docs/THEME-AUTHORING.md | 74 +- docs/THIRD_PARTY_NOTICES.md | 42 +- docs/UPSTREAM_SYNC.md | 122 ++-- dotnet-tools.json | 1 + scripts/preflight.sh | 2 +- 53 files changed, 3501 insertions(+), 2630 deletions(-) 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"