docs: unify documentation and streamline code comments
- Translated project documentation (LEARNING-JOURNEY, CONTRIBUTORS, AI_DISCLOSURE) to English for better accessibility. - Standardized internal code documentation by converting XML-doc blocks to standard comment format. - Cleaned up inline comments and removed redundant versioning metadata across the codebase. - Refactored non-functional text elements to improve readability and maintain a consistent style.
This commit is contained in:
+218
-223
@@ -1,336 +1,331 @@
|
||||
# Entwicklungsgeschichte und Lernprozess
|
||||
# Development History and Learning Process
|
||||
|
||||
## Hintergrund
|
||||
## Background
|
||||
|
||||
Ich bin Autodidakt. Hellion Chat ist mein erstes FFXIV-Plugin und mein erstes größeres C#-Projekt. Mein beruflicher
|
||||
Hintergrund ist Webentwicklung (Next.js, React, TypeScript, Prisma, MySQL), also Browser-Welt mit JavaScript-Toolchain.
|
||||
C# kannte ich vor diesem Projekt nur oberflächlich, ImGui gar nicht, Dalamud nur als Endnutzer über andere 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.
|
||||
|
||||
Wenn ich an einer Stelle nicht weiterkomme, nutze ich AI-Tools wie Claude Code als Pair-Hilfsmittel. Wie das genau
|
||||
aussieht und welche Klassifikation ich verwende, steht transparent 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).
|
||||
|
||||
---
|
||||
|
||||
## Warum überhaupt ein Chat-Plugin?
|
||||
## Why a chat plugin at all?
|
||||
|
||||
Hellion Chat soll Chat 2 nicht ersetzen. Chat 2 liefert ein vollständiges Chat-Erlebnis mit kompletter Historie,
|
||||
Filtern, Suche und Replay. Für die meisten Nutzer ist genau das richtig.
|
||||
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.
|
||||
|
||||
### Zwei Millionen Nachrichten in zwei Jahren
|
||||
### Two million messages in two years
|
||||
|
||||
Mein Wunsch nach einem engeren Default war ehrlich gesagt erstmal persönlich. Nach zwei Jahren mit Chat 2 lag meine
|
||||
Datenbank bei über zwei Millionen Nachrichten, der Großteil davon /say, /shout und /yell von wildfremden Leuten in
|
||||
Limsa. Genau diese Daten machen Chat 2's Voll-Historie nützlich, und die meisten Nutzer behalten sie auch gerne. Mein
|
||||
eigener Geschmack wollte einen kleineren Default. Also habe ich diesen Fork gebaut.
|
||||
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 mehreren Clubs
|
||||
### Greeter in several clubs
|
||||
|
||||
Dazu kam ein zweiter Use-Case: Ich bin in mehreren FFXIV-Clubs als Greeter aktiv. Für die Greeter-Arbeit reicht die
|
||||
Vanilla-Chat-Oberfläche nicht. Parallel laufende /tell-Gespräche schreiben in einem einzigen Tab durcheinander, und ich
|
||||
verliere ständig den Faden, wer mir gerade was geschrieben hat. Auto-Tell-Tabs (eines der frühen Hellion-Chat-Features)
|
||||
ist genau für diesen Workflow entstanden: ein Tab pro Gesprächspartner, automatisch gespawnt, mit manuellem
|
||||
Greeted-Status. Dass das auch der Privacy-Hygiene gut tut, war ein netter Bonus, nicht der Auslöser.
|
||||
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
|
||||
|
||||
Die Privacy-Defaults sind außerdem eine Position aus meinem Hauptberuf. Hellion Online Media ist mein Einzelunternehmen,
|
||||
und Datenschutz gegenüber Kunden ist da kein Marketing-Slogan, sondern operativ relevant. Dieser Fork ist die
|
||||
Plugin-Form derselben Haltung.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Warum nicht beim Original mitarbeiten?
|
||||
## Why not contribute to the original?
|
||||
|
||||
Drei Gründe, in absteigender Wichtigkeit.
|
||||
Three reasons, in descending order of importance.
|
||||
|
||||
### Defaults sind nicht verhandelbar, auch nicht meine
|
||||
### Defaults are not negotiable, including mine
|
||||
|
||||
Privacy-First als Standard ist eine Minderheits-Position. Chat 2 bedient zu Recht die breite Masse mit Voll-Historie als
|
||||
Default. Diese Defaults im Upstream zu ändern wäre falsch gewesen. Ich hätte den Standard für eine große Nutzerbasis
|
||||
umgekippt, die ihn so wollte, wie er ist. Saubere Trennung über einen eigenen Plugin-Slot war der respektvollere Weg.
|
||||
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.
|
||||
|
||||
### Das Webinterface musste weg
|
||||
### The web interface had to go
|
||||
|
||||
Das ist ein zentrales Chat-2-Feature für Remote-Zugriff vom Zweitgerät. Ein PR der das entfernt, hat in einem gepflegten
|
||||
Upstream-Projekt keine Chance, und das ist auch richtig so. Aber genau das Webinterface kollidiert mit der
|
||||
Privacy-First-These dieses Forks: Ein Chat-Plugin das einen lokalen HTTP-Server startet, ist für mein Threat-Model eine
|
||||
zu große Angriffsfläche. Also raus damit.
|
||||
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.
|
||||
|
||||
### Tempo
|
||||
### Velocity
|
||||
|
||||
Ein Solo-Maintainer-Projekt mit kleinem Tester-Pool kann schneller iterieren als ein etabliertes Plugin mit großer
|
||||
Nutzerbasis. Das ist kein Vorwurf an Upstream, sondern eine andere Optimierung. Ich brauche keine Roadmap-Abstimmung,
|
||||
keine Reviewer-Verfügbarkeit, und kann Audit-Konsequenzen wie das Webinterface-Removal in einer einzigen Version
|
||||
durchziehen statt über mehrere 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 erlaubt das alles ausdrücklich, mit klarer Attribution. Der Code liegt offen unter derselben Lizenz wie Chat 2.
|
||||
Infi, Anna oder sonst jemand dürfen reinschauen, Ideen mitnehmen, Fragen stellen oder den Fork einfach ignorieren. Alles
|
||||
drei ist für mich okay.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Wie ich so schnell release
|
||||
## How I release this fast
|
||||
|
||||
Wer auf den Repo schaut, sieht in kurzer Zeit viele Releases und sehr viele Commits. Beides wird von außen gerne als
|
||||
Red-Flag gelesen: KI-Slop, Salami-Taktik, Code-Spam. Bei Hellion Chat ist beides eine bewusste Entscheidung, und ich
|
||||
erkläre lieber einmal warum, als mich später dafür zu rechtfertigen.
|
||||
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.
|
||||
|
||||
### Vorarbeit, lange bevor der Fork existierte
|
||||
### Groundwork, long before the fork existed
|
||||
|
||||
Bevor ich die erste Zeile in `HellionChat/` getippt habe, war ich wochenlang nur Leser. Chat 2 ingame nutzen und damit
|
||||
rumspielen. Issues im Upstream-Tracker durchgehen, vor allem die geschlossenen, weil dort steht, wie Infi und Anna Bugs
|
||||
einkreisen. Commits lesen, gerne auch ältere, um zu verstehen, warum eine Architektur-Entscheidung getroffen wurde,
|
||||
nicht nur, dass sie getroffen wurde. Wenn ich heute weiß, wo im Code was liegt, dann nicht, weil ich besonders schnell
|
||||
durch eine Codebase navigiere, sondern weil ich den Code vorher gelesen habe.
|
||||
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.
|
||||
|
||||
Klingt nach Selbstverständlichkeit, ist es aber nicht. Die übliche Reihenfolge bei Solo-Forks heißt erst forken, dann
|
||||
verstehen. Ich habe es andersrum gemacht.
|
||||
That sounds obvious. It is not. The usual order for solo forks is fork first, understand later. I did it the other way
|
||||
around.
|
||||
|
||||
### Die Codebase von Infi und Anna
|
||||
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.
|
||||
|
||||
Hellion Chat baut auf einem Boden auf, der schon flach ist. Chat 2 ist sauber strukturiert, die Naming-Konventionen sind
|
||||
konsistent, die Trennung zwischen Layern (Storage, UI, Game-Hooks, IPC) ist klar gezogen. Das ist in
|
||||
Open-Source-Plugin-Welten nicht selbstverständlich, und es ist der Hauptgrund, warum sich Hellion-spezifische Features
|
||||
oft "fast nativ" einbauen lassen. Ich muss nicht erst Spaghetti entwirren bevor ich was Eigenes danebenstellen kann.
|
||||
### Infi and Anna's codebase
|
||||
|
||||
Side-Fact: Selbst beim ersten Codebase-Walkthrough mit Claude kam mehrfach der Hinweis, dass die Architektur
|
||||
ungewöhnlich gut aufgeräumt ist und mehrere Erweiterungspunkte vorbereitet. Das hat Gewicht, weil es von außen kommt,
|
||||
aber den eigentlichen Kredit kriegen Infi und Anna, nicht Claude.
|
||||
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.
|
||||
|
||||
### Atomar arbeiten, kleine Commits
|
||||
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.
|
||||
|
||||
Ein Commit, eine logische Änderung. Wenn ich einen Bug fixe, parallel eine Variable umbenenne und nebenbei einen
|
||||
Kommentar einbaue, sind das drei Commits, nicht einer. Klingt nach Mikro-Management, ist es aber nicht. Wenn in sechs
|
||||
Monaten ein Bug auftaucht und ich `git bisect` brauche, finde ich die kaputte Änderung in zwei Minuten statt in zwei
|
||||
Stunden. Bei einem 4000-Zeilen-Mega-Commit darf ich raten, welche der hundert Änderungen die kaputte ist.
|
||||
### Atomic work, small commits
|
||||
|
||||
Den Stil habe ich bewusst auch deshalb beibehalten, weil Infi im Upstream häufig genauso arbeitet. Manchmal ein
|
||||
Sechs-Zeilen-Commit, manchmal nur ein Typo-Fix. Das ist keine Schwäche, das ist eine Entscheidung für lesbare
|
||||
Git-History. Den Stil im Fork beizubehalten ist ein Respekt-Move: Wer die beiden Repos vergleicht, soll den gleichen
|
||||
Lese-Rhythmus haben.
|
||||
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.
|
||||
|
||||
Bonus für mich persönlich: Kleine Commits zwingen mich, jeden Schritt einzeln zu durchdenken und zu benennen. Wenn ich
|
||||
nicht in zwei Sätzen erklären kann, was ein Commit macht, ist die Änderung wahrscheinlich noch nicht klar genug. Auf
|
||||
Beginner-Niveau ist das ein eingebauter Sanity-Check, den ich bei einem Big-Bang-Commit nicht hätte.
|
||||
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.
|
||||
|
||||
### AI als Beschleuniger, ehrlich
|
||||
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.
|
||||
|
||||
Ja, AI hilft beim Tempo, und nicht zu knapp. Ohne CodeRabbit hätte ich Critical-Bugs der Klasse
|
||||
`Equals/GetHashCode`-Anti-Pattern, Hook-Subscription-Leaks und TOCTOU-Races nicht gefunden. Ich bin schlicht zu
|
||||
unerfahren für diese Klasse von Findings, das schreibe ich genau so hin.
|
||||
### AI as an accelerator, honestly
|
||||
|
||||
Was ich aber nicht mache: blind Code übernehmen, weil ein Tool ihn als Fix markiert hat. Bei mehreren
|
||||
CodeRabbit-Findings stand in den Original-Commits von Infi oder Anna sogar ein Stackoverflow-Link mit Begründung dabei,
|
||||
warum eine bestimmte Stelle so aussieht wie sie aussieht. Die habe ich gelesen, bevor ich was geändert habe. Erst
|
||||
verstehen, dann anfassen, dann committen. Das ist der Unterschied zwischen "AI gibt mir Code, ich pushe" und "AI zeigt
|
||||
mir wo's klemmt, ich entscheide".
|
||||
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.
|
||||
|
||||
Klassifikation und konkrete Beispiele zur AI-Nutzung stehen in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). Hier in dieser
|
||||
Sektion ging es nur um den Tempo-Aspekt: Recherche plus saubere Codebase plus atomare Commits plus AI-gestütztes
|
||||
Review-Sparring sind die vier Faktoren zusammen. Kein einzelner davon erklärt das Tempo allein.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Vom Web-Stack zu C# / Dalamud
|
||||
## From the web stack to C# / Dalamud
|
||||
|
||||
### Type-System? Weniger Schock als erwartet
|
||||
### Type system? Less of a shock than expected
|
||||
|
||||
C# nach TypeScript war angenehmer als gedacht. Properties statt getter/setter sind sauber, nullable reference types
|
||||
fühlen sich an wie `strict: true` in TypeScript. Ungewohnt war Wert-Typen vs. Referenz-Typen explizit denken zu müssen
|
||||
(`struct` vs. `class` mit echten Verhaltens-Konsequenzen), und Generics mit Constraints sind syntaktisch anders genug,
|
||||
dass ich beim Lesen kurz stocke. `async`/`await` ist semantisch ähnlich, aber Threading-Modelle sind in C# expliziter:
|
||||
`Task.Run`, `ConfigureAwait`, Synchronization-Contexts. Das hat mich mehrere Bugs gekostet, bevor ich verstanden hatte,
|
||||
wann der Main-Thread (in Plugin-Welt: der Framework-Tick) wirklich kritisch ist.
|
||||
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: ähnlich, aber anders
|
||||
### Build toolchain: similar, but different
|
||||
|
||||
`dotnet` CLI, csproj-XML, NuGet sind funktional nicht weit weg von npm und tsconfig. Aber das XML-Format der csproj ist
|
||||
eine andere Sprache als JSON-Configs. Die Lock-Datei (`packages.lock.json`) musste ich erst aktiv aktivieren
|
||||
(`RestorePackagesWithLockFile=true`); das ist nicht Default. Im Web-Stack ist Lock-File-First Standard, im .NET-Stack
|
||||
offenbar nicht. Das war eine echte Überraschung.
|
||||
`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 ist eine andere Welt
|
||||
### ImGui is a different world
|
||||
|
||||
Immediate-Mode-Rendering hat mit React-Component-Trees nichts gemein. Es gibt keine virtuelle DOM, keine Reconciliation,
|
||||
keinen "State der Komponente". Pro Frame zeichnet der Code die UI komplett neu, und der State lebt entweder in lokalen
|
||||
Variablen, die ich selbst verwalten muss, oder in der ImGui-eigenen ID-Stack-Logik.
|
||||
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.
|
||||
|
||||
Was in React zwei Zeilen `useState` sind, ist in ImGui ein Member-Field plus manuelle ID-Stempel auf den Widgets, sonst
|
||||
kollidieren zwei Selectables in derselben Loop, weil sie auf die gleiche ID zurückfallen. Die ID-Stack-Kollision in
|
||||
`SearchSelector` (gefixt in v1.0.0) war genau dieses Symptom: Alle Selectables fielen auf dieselbe ambiguous ID zurück,
|
||||
bis ich den Row-Index in den Push-ID gemixt habe. Klassischer "warum klickt der falsche Eintrag"-Bug, den man nur
|
||||
findet, wenn man verstanden hat, wie ImGui IDs intern handhabt.
|
||||
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-Spezifika
|
||||
### Dalamud specifics
|
||||
|
||||
Plugin-Lifecycle, IPC-Subscriber-Pattern, Hook-System für Game-Functions, Game-Object-Threading. Viel davon war nur
|
||||
durch Lesen der Upstream-Codebase und durch [dalamud.dev](https://dalamud.dev) zu verstehen. Meine Trainings- und
|
||||
Such-Ergebnisse für "Dalamud" liefern oft veraltete API-Beispiele aus alten Versionen. dalamud.dev ist die zuverlässige
|
||||
Quelle. Wenn jemand neu anfängt: dort hin, nicht zu 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.
|
||||
|
||||
### Der Tag, an dem mich der DalamudPackager einen Tag gekostet hat
|
||||
### The day DalamudPackager cost me a day
|
||||
|
||||
Dalamud SDK 15 liefert seinen eigenen Default-Packager mit, der Icons und Image-URLs ins Manifest einträgt. Ich hatte
|
||||
aus dem Upstream-Repo eine eigene `DalamudPackager.targets`-Datei mit `HandleImages`-Override übernommen, und die hat
|
||||
den SDK-Default überschrieben. Resultat: Das Manifest hatte keinen `IconUrl` mehr, und das Plugin tauchte in der
|
||||
Plugin-Liste ohne Icon auf.
|
||||
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.
|
||||
|
||||
Symptom war einfach zu sehen, Ursache hat einen Tag gekostet. Ich hatte die Override-Datei für eine Pflicht-Datei
|
||||
gehalten, war sie aber nicht. Removal in v0.5.2, seitdem läuft der SDK-Default. Lektion: Erstmal mit Defaults arbeiten,
|
||||
Overrides erst wenn der Default nachweislich nicht passt.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Was ich aus dem Fork gelernt habe
|
||||
## What I learned from the fork
|
||||
|
||||
### Refactor in einer fremden Codebase
|
||||
### Refactoring in an unfamiliar codebase
|
||||
|
||||
Der Standalone-Cut in v1.0.0 hat die `ChatTwo.*`-Identität komplett auf `HellionChat.*` migriert. Klingt nach
|
||||
Find-and-Replace. War es nicht.
|
||||
The standalone cut in v1.0.0 migrated the entire `ChatTwo.*` identity to `HellionChat.*`. That sounds like find and
|
||||
replace. It was not.
|
||||
|
||||
Konkret bedeutete das: Code-Namespace über alle 80 Source-Files plus 100 using-Direktiven plus zwei FQN-Aliases plus die
|
||||
Resource-Designer-Strings. Sechs IPC-Channels umbenannt (Breaking Change für Drittplugins, keine bekannten Anbindungen).
|
||||
Repo-Ordner-Struktur (`ChatTwo/` → `HellionChat/`) inklusive csproj, sln, allen GitHub-Workflows und der dependabot.yml.
|
||||
Public-Facing-Branding in README, repo.json, yaml auf Standalone-Framing umformuliert.
|
||||
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.
|
||||
|
||||
Das war kein Solo-Find-and-Replace, weil Unicode-String-Pfade in Workflow-YAMLs anders quotiert werden müssen als
|
||||
C#-Strings. Weil Resource-Designer-Files generierte Inhalte haben, die nicht jede Toolchain im Blick hat. Und weil die
|
||||
`ChatTwo.*`-IPC-Channel-Namen Strings in `GetIpcSubscriber`-Calls sind: kein Symbol, kein Compile-Error, wenn man einen
|
||||
vergisst. Da merkst du, was alles still bleibt.
|
||||
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.
|
||||
|
||||
### Sicherheit ist kein abstraktes Thema mehr
|
||||
### Security is no longer abstract
|
||||
|
||||
Vor diesem Projekt war Supply-Chain-Sicherheit für mich akademisch. Drei konkrete Lektionen haben das geändert.
|
||||
Before this project, supply chain security was academic for me. Three concrete lessons changed that.
|
||||
|
||||
**SQLite-Native-Binary.** Ich musste auf 3.50.3 pinnen (`SQLitePCLRaw.lib.e_sqlite3` Override), weil
|
||||
`Microsoft.Data.Sqlite` die transitiv nachgezogene Lib in einer Version mitschleppte, die CVE-2025-6965
|
||||
(Memory-Corruption durch Aggregate-Term-Overflow) und CVE-2025-7709 enthielt. Der Managed-Wrapper war neu, die
|
||||
Native-Lib war es nicht. Lektion: Transitive Dependencies prüfen sich nicht von selbst, du musst hinschauen.
|
||||
**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` honored bei `dotnet restore` (per `RestorePackagesWithLockFile=true` in der
|
||||
csproj) verhindert, dass transitive Versionen zwischen meiner Maschine und CI silent driften. Erst nach einem
|
||||
Build-Output-Mismatch zwischen lokal und GitHub-Actions hatte ich überhaupt verstanden, warum das nicht der Default ist.
|
||||
**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 und der CodeQL-Alarm der drei Releases gekostet hat.** CodeQL hat in `ImGuiUtil.WrapText` einen
|
||||
Critical-Alert wegen "unvalidated local pointer arithmetic" geworfen. v0.5.2 hat einen Edge-Case validiert. Alert kam
|
||||
wieder. v0.5.3 hat den Buffer-Length via `GetByteCount` vor der Pointer-Math gecheckt. Alert kam wieder. v0.5.4 hat den
|
||||
ganzen Algorithmus auf `Span` und int-Offsets umgebaut, mit einem 16-KiB-Cap auf den ArrayPool-Rent. Erst da war Ruhe.
|
||||
**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.
|
||||
|
||||
Lektion: Wenn ein statischer Analyzer drei Mal hintereinander meckert, ist nicht der Analyzer überempfindlich. Die
|
||||
Datenflusslogik ist es.
|
||||
Lesson: when a static analyser complains three times in a row, the analyser is not oversensitive. The data flow logic
|
||||
is.
|
||||
|
||||
### CodeRabbit als externer Code-Reviewer
|
||||
### CodeRabbit as an external code reviewer
|
||||
|
||||
Der v1.0.0-Sweep hat 3 Critical und 21 Major Findings hochgespült. Drei Klassen davon waren besonders lehrreich:
|
||||
The v1.0.0 sweep surfaced 3 critical and 21 major findings. Three classes were particularly instructive:
|
||||
|
||||
- **`Equals`-Methoden die `GetHashCode()` vergleichen.** Klassisches Hash-Kollisions-Anti-Pattern. Klingt nach "ist doch
|
||||
egal, wenn Hashes gleich sind, sind die Objekte auch gleich", ist aber genau falsch. Hashes können kollidieren,
|
||||
Objekte sind dann nicht gleich.
|
||||
- **`Dispose`-Methoden die nur einen Teil der Subscriptions wieder abmelden.** Leak bei jedem Plugin-Reload. Im
|
||||
Nutzer-Alltag merkst du das nicht sofort, im Long-Running-Test schon.
|
||||
- **TOCTOU-Races.** Zwischen Bounds-Check und Read kann ein anderer Thread das Array unter dir austauschen
|
||||
- **`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`).
|
||||
|
||||
Davon hatte ich vorher bestenfalls die Theorie gelesen, nicht selbst diagnostiziert. CodeRabbit war für mich der Moment,
|
||||
wo "akademisches Wissen" zu "okay, das ist mein Code, das ist mein Bug" wurde.
|
||||
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".
|
||||
|
||||
### Externe Tester sind ihr Gewicht in Gold wert
|
||||
### External testers are worth their weight
|
||||
|
||||
Carlas Feedback zur Pop-Out-Discoverability hat den Header-Button in v0.6.1 ausgelöst. Dass Pop-Outs nur per Rechtsklick
|
||||
erreichbar waren, hatte ich als Maintainer nicht mehr gesehen, ich kannte den Pfad blind. Carls Wunsch nach
|
||||
Theme-Varianten mit Helligkeits-Abstufungen hat mein Verständnis von "ein Theme = eine Farbe" auf "Theme-Familien mit
|
||||
Stimmungs-Varianten" verschoben. Jingliu hat TempTell-Persistence gefordert, was das Tab-System architektonisch in Frage
|
||||
stellt.
|
||||
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 hätte ich diese drei Dinge nicht erkannt. Punkt.
|
||||
Solo I would not have seen any of those three things. Full stop.
|
||||
|
||||
### release.yml und die Markdown-Hölle
|
||||
### release.yml and the YAML rabbit hole
|
||||
|
||||
Der `release.yml`-Workflow ist beim ersten v0.6.0-Tag-Push einfach nicht losgegangen. Ich habe Stunden in Permissions,
|
||||
Secret-Scopes und Tag-Trigger-Konfiguration gegraben, bevor ich verstand, was eigentlich los war: Der
|
||||
PowerShell-Heredoc-Footer im "Generate release body"-Step enthielt eine `---`-Markdown-Horizontal-Rule an Spalte 1, und
|
||||
genau das hat das YAML-Block-Scalar von `run: |` beendet. GitHub konnte die Workflow-Datei nicht parsen, also hat der
|
||||
Push-Tag-Trigger nie registriert.
|
||||
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: Footer in eine externe `.github/release-footer.md` extrahiert, Workflow liest sie via `Get-Content` ein. Lektion:
|
||||
Wenn ein Workflow nicht triggert, verifiziere als Erstes, dass GitHub die Datei überhaupt parsen kann. Das war einer der
|
||||
Bugs, bei denen ich nach dem Fix kurz gelacht habe und mich dann gefragt, wie viele andere YAML-Dateien ich noch habe,
|
||||
die so eine Falle drin haben könnten.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Was ich noch lerne
|
||||
## What I am still learning
|
||||
|
||||
### Performance-Profiling im Game-Context
|
||||
### Performance profiling in a game context
|
||||
|
||||
Der FPS-Drop-Bug aus Upstream Chat 2 ([#145](https://github.com/Infiziert90/ChatTwo/issues/145)) ist auch in Hellion
|
||||
Chat noch nicht reproduziert oder verifiziert. v1.0.0 hat mehrere Fixes auf den verdächtigen Pfaden (DbViewer O(N²) →
|
||||
O(N), AutoTranslate Lock-Serialisierung, EmoteCache HttpClient-Reuse), aber das systematische Vermessen unter Last fehlt
|
||||
mir. Ich muss noch lernen, wie man im Plugin-Kontext sauber misst, was wirklich das Frame-Budget frisst.
|
||||
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 und Pointer-Math
|
||||
### Native interop and pointer math
|
||||
|
||||
Auch nach dem WrapText-Span-Refactor in v0.5.4 ist mir Pointer-Math unsicher. ImGui zwingt einen an mehreren Stellen in
|
||||
`unsafe`-Code, und der Sicherheitsabstand zur "unbounded ArrayPool allocation"-Klasse von Bugs ist schmaler als mir lieb
|
||||
ist. Da will ich besser werden, bevor ich tieferes ImGui-Custom-Drawing anfasse.
|
||||
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-Disziplin für Plugin-Code
|
||||
### Test discipline for plugin code
|
||||
|
||||
Aktuell hat das Repo kein Test-Projekt. Das ist eine bewusste Entscheidung, keine vergessene. Plugin-Code mit
|
||||
FFXIV-Hooks und Dalamud-Lifecycle sauber zu testen ist nicht trivial, und ich hatte keinen Ansatz gefunden, der ohne
|
||||
riesiges Mocking-Gerüst sinnvoll wirkte. Privacy-Filter und Configuration-Migration wären gute Testkandidaten, weil sie
|
||||
isoliert sind. Steht auf der Liste, ist aber kein 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-Eigenheiten unter Wine
|
||||
### Linux quirks under Wine
|
||||
|
||||
XDG-Compliance, libnotify-Integration, WireGuard-Network-Detection, alles in der [Roadmap](ROADMAP.md), und alles
|
||||
technisch noch nicht ganz klar. Wine und sandboxed Plugin-Code teilen nicht alle System-APIs, und ich weiß nicht, wo die
|
||||
Stolperfallen liegen, bevor ich sie gefunden habe.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Einsatz von AI-Tools
|
||||
## Use of AI tools
|
||||
|
||||
Ich verwende Claude Code als Hilfsmittel, nicht als Ersatz für eigene Arbeit.
|
||||
I use Claude Code as an assistant, not as a replacement for my own work.
|
||||
|
||||
**Wofür ich AI einsetze:**
|
||||
**What I use AI for:**
|
||||
|
||||
- Debugging von Problemen, bei denen ich nach längerer Eigenrecherche nicht weiterkomme
|
||||
- Mustererkennen über große Codebasen hinweg (z. B. der ChatTwo→HellionChat-Sweep über 80 Dateien)
|
||||
- Verständnisfragen zu C#- und Dalamud-Konzepten, die mir noch nicht geläufig sind
|
||||
- Code-Review-Sparring, bevor ich CodeRabbit drauflasse
|
||||
- Debugging problems where I am stuck after extended research of my own
|
||||
- Pattern recognition across large codebases (e.g. the ChatTwo -> HellionChat sweep across 80 files)
|
||||
- Understanding questions on C# and Dalamud concepts I am not yet familiar with
|
||||
- Code review sparring before I run CodeRabbit on something
|
||||
|
||||
**Was ich selbst mache:**
|
||||
**What I do myself:**
|
||||
|
||||
- Architektur und Designentscheidungen
|
||||
- Privacy-First-Defaults und das Threat-Model dahinter
|
||||
- Tester-Kommunikation und Roadmap-Priorisierung
|
||||
- Reviewen, Verifizieren, Pushen
|
||||
- Architecture and design decisions
|
||||
- Privacy-first defaults and the threat model behind them
|
||||
- Tester communication and roadmap prioritisation
|
||||
- Reviewing, verifying, pushing
|
||||
|
||||
Die Klassifikation und konkrete Beispiele stehen in [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md). Mir ist wichtig, dass Nutzer
|
||||
und potenzielle Beiträger verstehen, wie der Code zustande gekommen ist, gerade bei einem Plugin, das mit Nutzerdaten
|
||||
arbeitet.
|
||||
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.
|
||||
|
||||
Ja, AI. Ja, alleine. Beides öfter erwähnt als nötig. Willkommen im Open-Source-Plugin-Klima.
|
||||
Yes, AI. Yes, alone. Both mentioned more than strictly necessary. Welcome to the open-source plugin climate.
|
||||
|
||||
---
|
||||
|
||||
## Warum diese Transparenz
|
||||
## Why this transparency
|
||||
|
||||
Wer sich den Quellcode ansieht, soll wissen:
|
||||
Anyone reading the source code should know:
|
||||
|
||||
- Ich bin kein professioneller C#- oder Plugin-Entwickler und lerne weiterhin dazu
|
||||
- AI-Unterstützung ist ein Werkzeug, kein Ghostwriter
|
||||
- Die Privacy-Position, die Designentscheidungen und die Roadmap sind meine
|
||||
- Ich versuche, meinen Code so sauber und sicher zu halten, wie meine aktuellen Fähigkeiten es zulassen
|
||||
- I am not a professional C# or plugin developer and am still learning
|
||||
- AI assistance is a tool, not a ghostwriter
|
||||
- The privacy position, the design decisions and the roadmap are mine
|
||||
- I try to keep my code as clean and secure as my current skills allow
|
||||
|
||||
Hellion Chat ist auch ein Lernprojekt, und das soll man dem Repository ansehen dürfen.
|
||||
Hellion Chat is also a learning project, and that should be visible in the repository.
|
||||
|
||||
---
|
||||
|
||||
## Verlinkungen
|
||||
## Links
|
||||
|
||||
- [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md) — KI-Pair-Disclosure mit Klassifikations-Schema
|
||||
- [`CONTRIBUTORS.md`](CONTRIBUTORS.md) — wer hat dieses Plugin neben mir besser gemacht
|
||||
- [`../NOTICE.md`](../NOTICE.md) — Anerkennung an Infi und Anna für das Chat-2-Fundament
|
||||
- [`ROADMAP.md`](ROADMAP.md) — geplante Cycles und Themen
|
||||
- [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md) -- AI pair disclosure with classification schema
|
||||
- [`CONTRIBUTORS.md`](CONTRIBUTORS.md) -- who has made this plugin better alongside me
|
||||
- [`../NOTICE.md`](../NOTICE.md) -- attribution to Infi and Anna for the Chat 2 foundation
|
||||
- [`ROADMAP.md`](ROADMAP.md) -- planned cycles and topics
|
||||
|
||||
Reference in New Issue
Block a user