diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs
index d005aa8..5fdb909 100755
--- a/HellionChat/Configuration.cs
+++ b/HellionChat/Configuration.cs
@@ -395,9 +395,8 @@ public class Tab
public string Name = Language.Tab_DefaultName;
// v1.2.0 — optionaler FontAwesome-Glyph-Name. Null bedeutet:
- // Default-Mapping aus TabIconMapping greift (basiert auf Tab-Name
- // oder erstem SelectedChannels-Eintrag). User können hier per
- // Settings → Tabs einen eigenen Glyph setzen.
+ // Default-Mapping aus TabIconMapping greift (basiert auf Tab-Name).
+ // User können hier per Settings → Tabs einen eigenen Glyph setzen.
public string? Icon = null;
[Obsolete("Removed in favor of SelectedChannels")]
diff --git a/HellionChat/Ui/TabIconGlyphResolver.cs b/HellionChat/Ui/TabIconGlyphResolver.cs
new file mode 100644
index 0000000..f587c12
--- /dev/null
+++ b/HellionChat/Ui/TabIconGlyphResolver.cs
@@ -0,0 +1,78 @@
+namespace HellionChat.Ui;
+
+///
+/// Reine String-Resolver-Logik ohne Dalamud-Dependency. Bewusst in
+/// eigener Datei (Dependency-Boundary auf File-Level sichtbar), damit
+/// Tests (HellionChat.Tests, Microsoft.NET.Sdk ohne Dalamud-Reference)
+/// sie aufrufen können, ohne dass die JIT beim Methodenaufruf die
+/// Dalamud-Assembly laden muss.
+///
+/// Wird im Settings-UI (T7) für die Glyph-Picker-Combobox und im
+/// Render-Code indirekt über
+/// verwendet.
+///
+internal static class TabIconGlyphResolver
+{
+ ///
+ /// Picker-Options-Pool — Single Source of Truth für das Glyph-Set.
+ /// Reihenfolge ist die UI-Reihenfolge im Settings-Tab Icon-Combobox.
+ ///
+ public static readonly IReadOnlyList PickerOptions =
+ ["comment", "comments", "cog", "users", "user-friends", "link",
+ "envelope", "clock", "hashtag", "star", "heart", "bell",
+ "bookmark", "flag", "fire"];
+
+ ///
+ /// Glyph-Set, das überhaupt als Override akzeptiert wird. Aus
+ /// abgeleitet — KnownGlyphs nie
+ /// manuell pflegen.
+ ///
+ private static readonly HashSet KnownGlyphs =
+ new(PickerOptions, StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Tab-Name → Default-Glyph-Name. Tab.Name wird per Lokalisierung
+ /// gesetzt; wir matchen daher gegen einen Pool aus DE/EN-Synonymen.
+ ///
+ private static readonly Dictionary NameDefaults = new(StringComparer.OrdinalIgnoreCase)
+ {
+ ["allgemein"] = "comment",
+ ["general"] = "comment",
+ ["system"] = "cog",
+ ["free company"] = "users",
+ ["fc"] = "users",
+ ["gruppe"] = "user-friends",
+ ["group"] = "user-friends",
+ ["party"] = "user-friends",
+ ["linkshell"] = "link",
+ ["ls"] = "link",
+ ["cwls"] = "link",
+ ["tells"] = "envelope",
+ ["tell"] = "envelope",
+ };
+
+ ///
+ /// Test-Surface: Glyph-Name-Resolver ohne Dalamud-Dependency.
+ /// Reihenfolge:
+ /// 1. Tab.Icon-Override (falls gesetzt und nicht nur Whitespace):
+ /// a) bekannter Glyph → diesen Glyph
+ /// b) unbekannter Glyph → harter Fallback "hashtag" (User hat
+ /// bewusst etwas gesetzt, also überstimmt das die Defaults)
+ /// 2. Auto-Tell-Tab → "clock"
+ /// 3. Tab-Name-Default (-Lookup)
+ /// 4. Fallback "hashtag"
+ ///
+ public static string ResolveGlyphName(Tab tab)
+ {
+ if (!string.IsNullOrWhiteSpace(tab.Icon))
+ return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag";
+
+ if (tab.IsTempTab)
+ return "clock";
+
+ if (tab.Name is { } name && NameDefaults.TryGetValue(name, out var byName))
+ return byName;
+
+ return "hashtag";
+ }
+}
diff --git a/HellionChat/Ui/TabIconMapping.cs b/HellionChat/Ui/TabIconMapping.cs
index 259dcb4..208efee 100644
--- a/HellionChat/Ui/TabIconMapping.cs
+++ b/HellionChat/Ui/TabIconMapping.cs
@@ -8,21 +8,24 @@ namespace HellionChat.Ui;
/// User können in Settings → Tabs per Tab.Icon-Override eigene
/// FontAwesome-Glyphen setzen.
///
-/// Aufteilung:
-/// - +
-/// sind reine String-Logik und stehen aus Test-Sicht ohne
-/// Dalamud-Dependency zur Verfügung (siehe Hilfsklasse
-/// in derselben Datei, die ohne
-/// Dalamud-Imports auskommt).
-/// - liefert den FontAwesomeIcon-Enum-Wert
-/// für Render-Code (T3/T5/T7) und ist daher Dalamud-abhängig.
+/// Diese Klasse ist Dalamud-abhängig (FontAwesomeIcon-Enum). Die
+/// reine String-Resolver-Logik liegt bewusst in
+/// (eigene Datei, ohne
+/// Dalamud-Imports), damit Tests sie ohne Dalamud-Reference aufrufen
+/// können.
///
internal static class TabIconMapping
{
///
/// FontAwesome-Glyph-Name → Icon-Enum-Lookup. Wird für die
- /// Production-Resolve-API benötigt. Enthält nur Glyphen aus
- /// .
+ /// Production-Resolve-API benötigt.
+ ///
+ /// INVARIANTE: Jeder Key in muss auch in
+ /// stehen. Wird
+ /// ein Glyph zu PickerOptions hinzugefügt, aber nicht hier, fällt
+ /// die Override-Auflösung still auf
+ /// zurück (degraded, kein Crash). Build-Time-Enforcement ist nicht
+ /// möglich, weil PickerOptions ohne Dalamud-Reference auskommt.
///
private static readonly Dictionary GlyphLookup = new(StringComparer.OrdinalIgnoreCase)
{
@@ -43,22 +46,10 @@ internal static class TabIconMapping
["fire"] = FontAwesomeIcon.Fire,
};
- ///
- /// Picker-Options-Pool — Pass-through zu .
- ///
- public static IReadOnlyList PickerOptions => TabIconGlyphResolver.PickerOptions;
-
- ///
- /// Pass-through zu .
- /// Liegt hier nochmal als Convenience-Alias, damit Aufrufer nur
- /// kennen müssen.
- ///
- public static string ResolveGlyphName(Tab tab) => TabIconGlyphResolver.ResolveGlyphName(tab);
-
///
/// Production-Surface: liefert das Icon für einen Tab. Wrapper um
/// plus
- /// Enum-Lookup. Wird von Render-Code (T3, T5, T7) verwendet.
+ /// Enum-Lookup. Wird von Render-Code (T3, T5) verwendet.
///
public static FontAwesomeIcon Resolve(Tab tab)
{
@@ -68,78 +59,3 @@ internal static class TabIconMapping
: FontAwesomeIcon.Hashtag;
}
}
-
-///
-/// Reine String-Resolver-Logik ohne Dalamud-Dependency. Bewusst
-/// separat, damit Tests (HellionChat.Tests, Microsoft.NET.Sdk ohne
-/// Dalamud-Reference) sie aufrufen können, ohne dass die JIT beim
-/// Methodenaufruf die Dalamud-Assembly laden muss.
-///
-internal static class TabIconGlyphResolver
-{
- ///
- /// Glyph-Set, das überhaupt als Override akzeptiert wird. Spiegelt
- /// die Keys aus .
- ///
- private static readonly HashSet KnownGlyphs = new(StringComparer.OrdinalIgnoreCase)
- {
- "comment", "comments", "cog", "users", "user-friends", "link",
- "envelope", "clock", "hashtag", "star", "heart", "bell",
- "bookmark", "flag", "fire",
- };
-
- ///
- /// Picker-Options-Pool. Wird im Settings-Tab Icon-Combobox angezeigt.
- /// Reihenfolge ist die UI-Reihenfolge.
- ///
- public static readonly IReadOnlyList PickerOptions =
- ["comment", "comments", "cog", "users", "user-friends", "link",
- "envelope", "clock", "hashtag", "star", "heart", "bell",
- "bookmark", "flag", "fire"];
-
- ///
- /// Tab-Name → Default-Glyph-Name. Tab.Name wird per Lokalisierung
- /// gesetzt; wir matchen daher gegen einen Pool aus DE/EN-Synonymen.
- ///
- private static readonly Dictionary NameDefaults = new(StringComparer.OrdinalIgnoreCase)
- {
- ["allgemein"] = "comment",
- ["general"] = "comment",
- ["system"] = "cog",
- ["free company"] = "users",
- ["fc"] = "users",
- ["gruppe"] = "user-friends",
- ["group"] = "user-friends",
- ["party"] = "user-friends",
- ["linkshell"] = "link",
- ["ls"] = "link",
- ["cwls"] = "link",
- ["tells"] = "envelope",
- ["tell"] = "envelope",
- };
-
- ///
- /// Test-Surface: Glyph-Name-Resolver ohne Dalamud-Dependency.
- /// Reihenfolge:
- /// 1. Tab.Icon-Override (falls gesetzt):
- /// a) bekannter Glyph → diesen Glyph
- /// b) unbekannter Glyph → harter Fallback "hashtag" (User hat
- /// bewusst etwas gesetzt, also überstimmt das die Defaults)
- /// 2. Auto-Tell-Tab → "clock"
- /// 3. Tab-Name-Default (-Lookup)
- /// 4. Fallback "hashtag"
- ///
- public static string ResolveGlyphName(Tab tab)
- {
- if (!string.IsNullOrEmpty(tab.Icon))
- return KnownGlyphs.Contains(tab.Icon) ? tab.Icon : "hashtag";
-
- if (tab.IsTempTab)
- return "clock";
-
- if (NameDefaults.TryGetValue(tab.Name, out var byName))
- return byName;
-
- return "hashtag";
- }
-}