Semgrep rule IDs follow the pattern <pack>.<rule>. The pack name is
csharp.lang.security.sqli.csharp-sqli and the rule inside it is also
called csharp-sqli, so the full ID needs the trailing .csharp-sqli
again. Without it the exclude flag silently filters a different
subset of rules and the actual rule still runs.
Semgrep flags eight CommandText-with-string-interpolation call sites
in MessageStore.cs as SQL-injection patterns. All are safe in this
context: table names and clause fragments come from internal code
constants, the actual values are bound via SqlParameter, and the
plugin SQL surface is local-only with no external input vector.
CodeQL would not flag these because it does dataflow analysis and
sees the constants. Semgrep only matches patterns. Excluding the rule
for this repo only via the new semgrep-exclude-rules input keeps the
rule active for the other Hellion repos where it might catch real
issues (e.g. the web apps).
Calls JonKazama-Hellion/security-workflows for Semgrep SAST + Trivy
filesystem vulnerability scan. Runs on push to main/master, on every
PR, and weekly Monday 06:00 UTC.
Cleanup pass after the v1.4.3 cutover. Five files still carried
gitea.com hosts or dead github.com security-advisory links because
they were not touched in the prior URL sweep.
- forge-announce.yml: Discord embed avatar and tag link
- release-footer.md: custom-repo URL plus six doc/license links
- bug_report.yml, config.yml, PULL_REQUEST_TEMPLATE.md: replace
github.com/.../security/advisories/new with mailto:kontakt@
hellion-media.de. Gitea has no privately-reportable advisory
feature; e-mail is the closest functional equivalent.
Pure string replacement, no logic change.
Migrations: all current users are on schema v16, the v9 to v16 migration
chain ran in v1.2.1 and earlier. Replace the seven in-LoadAsync migration
blocks with a hard schema-gate in the Phase-1 ctor; older configs trigger
a clear "install v1.4.2 first" error. Code-hygiene change, fast-path
saving is negligible. Remove the now-unused TryReadPreV13ThemeOpacity
helper that only served the v13 to v14 block.
AutoTranslate.PreloadCache: was sync ~300 ms in LoadAsync. Move to
Task.Run so plugin-load returns ~300 ms earlier. Trade-off: first
auto-translate use of a session may have a sub-second hitch if the
cache hasn't finished warming. Acceptable, it is first-use cost
instead of every-load cost.
The previous fire-and-forget Task.Run pattern could leave Plugin.FontManager
null when the first UiBuilder.Draw tick fires (ChatLogWindow dereferences
FontManager.FontAwesome / RegularFont / ItalicFont in its draw paths).
Allocate FontManager and call BuildFonts() synchronously, mirroring
ChatTwo Plugin.cs:152. BuildFonts itself is non-blocking — it just
registers IFontHandles with Dalamud's atlas; the actual atlas rebuild
runs on Dalamud's pipeline a few frames later, so the perceived-load
win still holds (LoadAsync no longer waits for atlas build).
BuildFontsAsync in FontManager.cs stays for the Settings-driven manual
rebuild path.
Phase-1 was still doing 7 schema migrations and 25+ service allocations
synchronously, blocking the ctor return. Move all of that to LoadAsync,
keeping only bootstrap-essentials in the ctor: conflict detection,
config load, language init, ImGui init, WindowSystem skeleton.
Decouple the font task from the LoadAsync await — font-build runs
fire-and-forget, so first frames render with Dalamud's default font
until the Hellion-Exo2/NotoSans atlas rebuild completes (visible
"font-pop"). Mirrors ChatTwo's pattern; the perceived-load win comes
from "Finished loading" landing earlier, not from a faster atlas build.
Smoke test in Task 6 surfaced a NullReferenceException at Plugin.cs:885 —
the retention sweep was scheduled in Phase 1 but dereferences
MessageManager.Store, which is only allocated in Phase 2 (LoadAsync).
Move the call after MessageManager init. Drop the comment that wrongly
claimed independence from Phase-2 services.
I-1: rewrite property-shape comment to reflect that all properties (not
just Phase-2 ones) moved to { get; private set; } = null!;.
I-3: drop plan-jargon (Q1=A / Q3=B / Task 5) from source comments;
replace with durable rationale and a version-anchored TODO for the
FontManager.BuildFontsAsync follow-up.
I-4: remove German-word leak ("pflicht") from English comment in
DisposeAsync.
M-5: wrap each cleanup line inside Framework.RunOnFrameworkThread with
CaptureFailure so a single Dispose throw no longer strands subsequent
cleanup. Drops the inline try/swallow on SetChatInteractable. Mirrors
Lightless DisposeFrameworkBoundServicesAsync pattern.
actions/upload-artifact@v7 fails on Gitea Actions — the GitHub
artifact API has compatibility gaps the Gitea runtime layer does not
fully cover, and v7 specifically tripped exitcode 1 on the Strato
runner. The build itself runs fine; the artefact was never consumed
by anything (release.yml does its own latest.zip lookup), so the
cleanest fix is to make build.yml a pure compile-health check
without artefact upload.
Chat 2 has entered a major rework that Infi confirmed makes selective
patches no longer portable. The cherry-pick pipeline as a routine
workflow stops with the v1.4.x cycle. Documentation reflects the new
state across all touchpoints.
UPSTREAM_SYNC.md rewritten: replaces the "How I Cherry-Pick" /
"Reviewing What Is New Upstream" / "Conflict Handling" sections with
"Why Cherry-Picking Stopped", "What Closing the Pipeline Means in
Practice", "What Does Not Change", "What Could Re-Open Later".
Existing cherry-pick trails in the git history stay intact, EUPL-1.2
anchor lines and NOTICE.md remain canonical.
README.md, CONTRIBUTING.md, ROADMAP.md, THIRD_PARTY_NOTICES.md and
the PR template updated to match: cherry-pick references reframed as
historical or pointed at UPSTREAM_SYNC.md for the current state.
NOTICE.md keeps the BetterTTV cherry-pick example as a concrete past
case but adds a paragraph that the pipeline is closed and clarifies
the attribution standard is preserved unchanged.
PULL_REQUEST_TEMPLATE.md drops the "Upstream cherry-pick from Chat 2"
checkbox and the cherry-pick-path compatibility prompt. The upstream
git remote was already removed locally on 2026-05-08 (separate change,
not in this commit).
No source-file edits, no manifest version bump, no changelog entry —
this is documentation-only and ships with the next release.
- codeql.yml removed: GitHub-only (uses github/codeql-action/*).
- build.yml + release.yml: runs-on switched to ubuntu-latest (Gitea Cloud
has no Windows runner). Dalamud staging is now downloaded via curl/unzip
into $HOME/.xlcore/dalamud/Hooks/dev/, the path the Dalamud SDK 15 uses
on Linux. Locate-step uses find instead of Get-ChildItem.
- release.yml: softprops/action-gh-release replaced with the Gitea-native
https://gitea.com/actions/release-action. Auto-injected GITHUB_TOKEN on
Gitea Actions has Gitea-API scope and is sufficient.
- forge-announce.yml: environment: Webhook removed (Gitea has no
environments — DISCORD_FORGE_WEBHOOK is a repo-level Actions secret).
avatar_url and embed url switched from raw.githubusercontent.com /
github.com to gitea.com.
- release-footer.md: install URL plus the five doc links (README, PRIVACY,
THIRD_PARTY_NOTICES, SECURITY, SUPPORT) and LICENSE link switched to
gitea.com/.../src/branch/main/. ChatTwo upstream link stays on GitHub.
The comment on BrandingLinks claimed a follow-up housekeeping sweep was
"out of scope for this Cycle" — that Cycle framing no longer matches how
Plan v4 schedules the work. Trim the trailing clause; the rest of the
comment still documents the housekeeping intent.
Re-encodes the four existing screenshots and the docs/images forge banner
to 8-bit indexed-color PNGs. Total asset payload drops from ~3.87 MB to
~311 KB (92% smaller) without visible quality loss in the README/forge
post rendering.
Adds the four brand-logo variants designed by Florian Eck and credited
in COPYRIGHT (Visual assets section): the Hellion Online Media wordmark,
the square Hellion crest, the horizontal Hellion Forge color logo and
the Discord-sized hammer mark. All variants live in docs/images/ so the
forge post and README can reference them without polluting the in-game
plugin payload under HellionChat/images/.
Visual assets are NOT covered by the EUPL-1.2 source code licence; their
licensing terms are documented in COPYRIGHT.
Renames HellionChat/SelfTest/ to HellionChat/SelfTests/ (plural) to
match the folder convention used throughout the Build Suite Plan v4
Phase 6 file list. The singular name was introduced as a known
discrepancy in cb327b8 and is now resolved.
- git mv preserves full history via rename detection
- Namespace updated: HellionChat.SelfTest → HellionChat.SelfTests
- Plugin.cs qualifier updated: SelfTest. → SelfTests.
- Build: 0 errors, 0 warnings
Registers a single SelfTestStep that exercises Plugin.ThemeRegistry.Switch
through the live theme list. Verified in-game via /xldev SelfTest tab on
2026-05-08; Plugin loads cleanly with the RegisterTestSteps call and the
step runs the theme cycle as expected.
Folder is HellionChat/SelfTest/ (singular). Future steps may rename to
SelfTests/ to match the local Plan v4 convention.
ChatBox.SendMessage reads bytes from ValidateMessage so Encoding.UTF8.GetBytes
runs once per send. ValidateMessage takes an injectable sanitiser so xUnit can
exercise the length-equality gate without ClientStructs game memory.
CompactInputSubmitter and CompactInputHistoryNavigator lift the deterministic
parts of ChatInputBar's pop-out submit and history-up/down callback into POCO
helpers under HellionChat/_Helpers/. The ImGui buffer splice
(DeleteChars/InsertChars) stays at the call site because it needs the live
callback data.
Behavior is identical to the previous inline implementation; tests in the
local Build Suite repo pin the contracts.
Establishes the local pre-push gate. preflight.sh runs four blocks: version
consistency, manifest shape (Icon plus all ImageUrls), changelog sync, plus a
release build as compile-health smoke. setup-hooks.sh wires core.hooksPath to
.githooks. .gitignore opens scripts/ for tracking (setup-dev-env.sh stays
private). Test execution itself lives in a separate local repository and is
not part of this codebase.
Hellion Chat 1.4.0 — Critical Lifecycle Fixes
Seven P0 lifecycle and race bugs eliminated before any performance refactor.
Plus version bump, manifest sync, changelog, forge-post.
Match the new HellionChat comment-length convention: 1-3 lines for
standard pitfall notes, 5+ only for non-trivial workarounds. The
previous Dispose comment was 14 lines of textbook prose, which veered
into AI-slop territory and would rot on the next refactor.
Hab vergessen die repo.json wieder mit zu bumpen, deshalb hat
Dalamud den v1.2.2-Release nicht angenommen — komplette Manifest-
Bump-Checkliste diesmal durchgezogen: csproj, yaml-Description +
Changelog, repo.json (AssemblyVersion + TestingAssemblyVersion +
drei DownloadLink*-URLs + Description + Changelog), CHANGELOG.md,
ROADMAP.md, README.md, Forge-Post-Datei. Inhalt unverändert
gegenüber v1.2.2.
UI:
- SettingsOverview cards now wrap subtext to two lines (DrawList wrap-
width) and the card height grew from 96 to 110 px. Single-line
fitting clipped most of the bilingual subtitles.
- HellionStyle pushes ChildBg with alpha 0 when WindowOpacity < 1.0
to keep stacked BeginChild layers from compounding the deckgrade
past what the slider suggests.
- WindowOpacity slider helpmarker now points to Dalamud's per-window
hamburger menu for opacity / blur / pin / click-through overrides.
UX defaults (v15 → v16 migration adopts new values only when the user
is still on the previous default — bool flips are heuristic, the prior
defaults are from the v1.2.0 cycle and rarely toggled):
- UseCompactDensity false → true (single-line message style is cleaner)
- HideInNewGamePlusMenu false → true (consistent with other hide-flags)
- HideSameTimestamps false → true (cleaner log)
- MaxLinesToRender 5000 → 2500 (mid-range hardware friendlier)
- ChatColours empty → Hellion brand preset (the first-run wizard does
not offer a preset choice, so fresh installs get the brand colours
out of the box)
- Split Appearance into ThemeAndLayout (theme + window-style + timestamps)
and FontsAndColours (fonts + per-channel colours)
- Merge Database into DataManagement together with Retention/Cleanup/Export
from Privacy
- Move HistoryPreload from Privacy to Chat → Auto-Tell-Tabs
- Move KeybindMode from General/Language to General/Input
- Drop OverrideStyle, ChosenStyle, WindowAlpha, ShowThemeQuickPicker
- Migration v15 → v16 maps WindowAlpha → WindowOpacity if Opacity at default
- Add card-subtext per overview card so users do not have to guess where
a setting lives
Forge-Auto-Announce workflow failed twice on tag push because the
Discord webhook returned 400 — embed.description hit 5346 chars,
which exceeds Discord's hard 4096-per-field limit. The workflow's
own 5500-total cap (V6 check) didn't catch it because it was a
per-field overflow, not a total-payload overflow.
Both yaml changelog block and forge-post DE-body trimmed:
- yaml v1.2.0 EN-block: 3249 → 2104 chars
- forge-post DE-body: 2069 → 1543 chars
- description final: 3675 chars (with ~420 char headroom)
- total payload: 3740 / 5500
Plugin-Manager-facing changelog still covers all v1.2.0 highlights
plus the post-test bug fixes; just denser. Tag will be force-recreated
on this commit so workflow_dispatch picks up the trimmed files from
the v1.2.0 tag tree.
Backlog item: workflow should add a per-field cap check (4096 for
description, 1024 for field values) so future releases fail-fast
locally before hitting Discord.
27 commits brought in from feature/v1.2.0-layout-refresh:
- Sidebar/Top-Tabs visual modernisation (icon-only sidebar with
44px fixed width and tooltip, vertical accent pill, top-tab
underline pill).
- TabIconMapping with single-source 15-glyph pool, per-tab
Icon override via Settings → Tabs combobox.
- AutoTellTabTint hash-based icon+color differentiation
(84 distinct combinations) for parallel tells.
- Bottom status bar (22px): channel/privacy/counts/tells/version.
- Card-Rows as default message render with Compact-Density
opt-out toggle.
- Pulsing red unread-dot indicator on sidebar tab icons,
respects Configuration.ReduceMotion.
- Migration v14 → v15: legacy theme fields removed, Appearance
bindings cleaned to use Themes tab as single source.
- Settings-Save chat-history preservation: UpdateFrom Identifier-
mapping for persistent tabs, TempTab skip in ClearAllTabs/
FilterAllTabs, conditional refilter only for filter-relevant
changes.
- Hellion font (Exo 2) no longer blocks FontSizeV2 adjustment —
4K user can scale up the variable font.
Tag v1.2.0 sits on the last feature commit (3da550c).
Forge-Auto-Announce-Action triggers on tag push.
Settings → Erscheinungsbild → Schriftarten: bei aktiver
'Mitgelieferte Hellion-Schrift (Exo 2) verwenden' war der
Schriftgrößen-Slider ausgegraut und FontSizeV2 wurde im
FontManager auch nicht angewendet — 4K-User konnten den
Plugin-Font nicht hochskalieren.
Exo 2 ist Variable-Font, FontSize ist also problemlos
adjustierbar. Zwei-teiliger Fix:
- Appearance.cs: UseHellionFont rendert jetzt nur FontSizeCombo +
SymbolsFontSizeCombo, kein Disabled-Wrap mehr. Der Bestand-
Custom-Font-Stack mit FontsEnabled-Toggle und Font-Choosern
bleibt exclusive zur Hellion-Schrift, läuft im else-Pfad.
- FontManager.cs RegularFont-Build: SizePt-Source verzweigt
jetzt auf UseHellionFont — Hellion-Pfad nutzt FontSizeV2,
Bestand-Pfad nutzt weiter GlobalFontV2.SizePt aus dem
Custom-Font-Spec.
Reported by Flo 2026-05-06: '4k monitor ... der standart zu klein'.
Settings.Save() unconditionally ran ClearAllTabs + FilterAllTabsAsync
after every save. The cycle reloads messages from the DB, which silently
wipes any in-session message that wasn't persisted — Privacy-First
configurations block most channels from the DB, so all unlogged
channels (Allgemein/Say/Yell/Shout under default filters) showed up
empty after every settings save.
New HasFilterRelevantChanges helper compares Mutable to Plugin.Config
across:
- PrivacyFilterEnabled
- PrivacyPersistChannels (HashSet<ChatType>)
- PrivacyPersistUnknownChannels
- FilterIncludePreviousSessions
- per-persistent-tab: Identifier (reorder/swap), SelectedChannels,
ExtraChatAll, ExtraChatChannels
Refilter only runs if any of those changed. Cosmetic settings (theme,
tab icons, layout, fonts, language) leave the chat log untouched.
Combined with the prior UpdateFrom Identifier-mapping fix and the
TempTab skip in ClearAllTabs/FilterAllTabs, both persistent and
Auto-Tell tabs now fully survive a settings save.
Reported by Flo from in-game testing 2026-05-05/06: 'der allgemein
chat tab z.b immernoch gecleart wird' / 'alle vom plugin nicht
geloggten channel sind dann leer'.
Also updated yaml changelog, docs/CHANGELOG.md and .github/forge-posts/
v1.2.0.md to describe the actual fix shape rather than the partial
UpdateFrom-only fix that preceded it.
Final release-doc pass for v1.2.0:
- yaml changelog extended with the post-T14 polish notes
(Auto-Tell variety, unread pulse, layout fixes, settings-save
wipe fixes for both persistent and TempTabs)
- docs/CHANGELOG.md date filled in (2026-05-05) and same polish
notes added under Added/Fixed sections
- .github/forge-posts/v1.2.0.md created — DE bullet body, picked
up by forge-announce.yml on tag push (EN side reads the yaml
changelog block)
1920 chars in the forge post — comfortably under the 5500-char
total cap that the workflow enforces.
TempTabs (Auto-Tell-Tabs) are session-only and populated directly by
AutoTellTabsService.HandleTell — they have no DB persistence to refilter
from. The Settings-save flow calls ClearAllTabs() + FilterAllTabsAsync()
to rebuild persistent tabs after potential filter changes; this wiped
TempTabs as collateral because their tells aren't in the DB (either
Privacy-filtered out or simply not yet persisted).
Skip TempTabs in both methods so their live-state survives any settings
save. Live tells continue to land via HandleTell, regardless of the
clear/filter cycle.
UpdateFrom replaced persistent tabs with Tab.Clone()s. Clone deliberately
omits the NonSerialized Messages list to avoid shared mutable state on
disk-load — but on a settings save (Plugin.Config.UpdateFrom(Mutable))
that path means every persistent tab loses its in-session chat history
the moment the user clicks Save.
Capture the live MessageList plus LastSendUnread counter by Identifier
before the replace and restore them onto the cloned tabs. Tab.Clone()
already preserves Identifier so the lookup matches one-to-one for
unchanged tabs. New tabs added in settings get a fresh empty list,
deleted tabs lose their history (both intended).
Reported by Flo in-game 2026-05-05 — chat got wiped on every settings
save during v1.2.0 testing in Limsa.
Sin-based alpha scaling between 60% and 100% with a 2-second cycle.
Subtle enough to register peripherally without becoming distracting.
Respects Plugin.Config.ReduceMotion (field exists since v1.1.0,
toggle UI lands in v1.3.0) — static render when disabled.