MessageStore.Connect used to call Util.IsWine() directly via a
DalamudUtil alias, which made the ctor unreachable from the xUnit
test AppDomain: any test that allocated a MessageStore tripped a
FileNotFoundException on Dalamud.dll before reaching the assertion.
The ctor now takes an IPlatformUtil and reads the cached IsWine
property. MessageManager passes Plugin.PlatformUtil in. Production
behaviour is identical; the test path can now substitute a fake
and exercise the SQLite migration logic in isolation.
Introduces a thin interface around Util.IsWine and Util.OpenLink so
services can be constructed in an isolated xUnit AppDomain without
forcing Dalamud.dll onto the assembly search path. Production wiring
(DalamudPlatformUtil) caches IsWine at ctor time — it's a runtime
probe that never changes for the lifetime of a plugin instance,
mirroring the Lightless DalamudUtilService pattern.
Plugin.PlatformUtil is wired in the Phase-1 ctor so any service that
LoadAsync allocates can resolve the platform indirection without
plumbing the instance through additional constructor params.
Follow-up commits route MessageStore and the OpenLink call-sites
through this interface.
The pre-serialization snapshot used to clone the entire Config.Tabs
list, then Clear/AddRange the snapshot back. With a typical config of
~30 user-defined tabs plus up to 15 session-only temp tabs, that's a
45-item clone on every save. The persistent tabs never leave the list
during this routine, so cloning only the temp subset is functionally
identical and keeps the allocation proportional to AutoTellTabsLimit.
The 2px underline pill was hardcoded — at 125/150% DPI the surrounding
tab layout scaled with ImGuiHelpers.GlobalScale but the pill stayed
2px, so the line landed on sub-pixel boundaries and rendered as a
fuzzy band. Now: height scales with GlobalScale (clamped to >=1px),
and the DrawList coordinates round to physical pixels via MathF.Round
so the rect aligns with the framebuffer grid.
DrawCard used to call ImGui.GetWindowDrawList once per card, so a frame
with 10 settings cards took 10 draw-list lookups. The list is the same
for every card in the same frame, so Draw() now resolves it once and
passes the pointer down. Pattern parity with ChatLogWindow's frame-local
draw-list handling.
HellionStyle.PushGlobal had two lines that resolved the child-bg alpha
based on window opacity. Moves the 0.999f threshold and the alpha-mask
into HellionStyleHelpers.ResolveChildBgAlpha so the logic is reachable
from the build suite without touching the ImGui surface.
BrandingLinks (5 Hellion-owned URLs) and IntegrationLinks (2 third-party
plugin URLs) now run through UrlValidation.ValidateAll from a
[ModuleInitializer] hook. A malformed URL throws InvalidOperationException
at plugin load with the source class and the broken URL in the message,
instead of silently failing when a user clicks the button.
CA2255 is suppressed at the attribute sites — the warning is for library
code shipped to unknown consumers, but the plugin DLL is loaded directly
by Dalamud, which makes module-init the right one-shot hook.
The atlas-toolkit pipeline can throw InvalidOperationException or
ArgumentException when a configured font is structurally broken (e.g.
unreadable header, unsupported glyph table). Previously only IO-shaped
throws routed to the NotoSansCjkRegular fallback, so a corrupt font
config would take down the entire atlas build instead of degrading
gracefully. The warning log now carries the exception type name so the
diagnostic path can tell which class of throw triggered the fallback.
Block E runs 'dotnet csharpier check' against the HellionChat/ tree,
catching reflow drift before push. Block F runs markdownlint-cli2 over
the repo's *.md files; MD036 is disabled because forge-post bodies use
bold emphasis as section headings (the auto-announce workflow renders
those as Discord embeds, so the bold pattern is required). The .claude
directory is excluded from the lint scope to match its gitignore status.
.markdownlint.json also gains MD024 with siblings_only:true so per-release
'### Internal' sub-headers in CHANGELOG.md don't trip the rule across
sibling H2 sections.
Whitespace and line-reflow drift from the markdown linter on the four
files touched by the versions-bump commit (forge-post, README,
CHANGELOG, ROADMAP). No content changes.
Pattern-adherence pass after the cycle's code commits:
- ChatLogWindow.cs: NotifiedDrawFailure renamed from
_notifiedDrawFailure. The file's per-window state flags (DrewThisFrame,
WasDocked, Activate, PlayedClosingSound, …) all use PascalCase
without underscore prefix; the new flag now matches that
- Plugin.cs: trim the session-only RemoveAll comment from 5 lines to 2
and add the standard TEST-MIRROR pointer line. Same shape as
AutoTellTabsService.cs:28 and the other six TEST-MIRROR sites
- InputHistoryService.cs: add the TEST-MIRROR pointer for the new
Build-Suite tests
Manifest sync across csproj, yaml, repo.json, README, CHANGELOG,
ROADMAP and the Plugin.cs schema-gate error message. ROADMAP also gets
the v1.4.4 release block that was missed in that cycle's closure.
Forge-post v1.4.5.md follows the established frontmatter + DE-body
convention; the EN block is sourced from the yaml changelog by the
forge-announce workflow.
Below roughly 340 px content width the version slot starts overlapping
the four slots to its left because the right-aligned SameLine still
plants the text where its baseline would have been. New 200 px width
threshold drops the version line entirely below that, so the other
slots stay readable. The version is back as soon as the window grows.
Expands the one-liner above Plugin.cs:167-168 to spell out *why* the
RemoveAll runs before AutoTellTabsService.Initialize: tells are
typically privacy-filtered, so resurrecting a tab from a crashed
session would trigger DB reconstruction on the next load. Also links
to the TEST-MIRROR pin in the Build-Suite for future readers.
GetHellionFontBytes used to throw a FileNotFoundException when the
embedded Hellion font resource was missing — only possible on a broken
csproj or a hand-rolled dev build, never on a signed release, but the
throw bubbled up and broke the entire UiBuilder font atlas.
Replaced with a nullable TryGetHellionFontBytes that logs a warning
and returns null on miss. The RegularFont delegate now falls back to
the same system-font path that UseHellionFont=false already uses, so
the plugin still loads and the issue surfaces in /xllog instead of as
a crash.
Static InputHistoryService entries used to survive a plugin reload
because static field state doesn't get cleaned up on its own. The new
Reset() method clears the list and is wired into Plugin.DisposeAsync
alongside the existing pure-memory cleanups, so the next plugin load
starts with an empty history instead of inheriting the previous
session's typed commands.
Splits accept from close: OnClose no longer silently sets
FirstRunCompleted, so the X-button 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 the wizard without picking a
profile; defaults stay active and the choice persists.
Strings are bilingual (EN + DE) with a tooltip explaining the
behaviour. Card height now reserves room for the footer separator.
Surfaces a per-session warning notification when DrawChatLog throws so
the user knows something went wrong instead of staring at an empty
window. Stack trace stays in /xllog as before. The one-shot guard
prevents the notification stack from flooding frame-by-frame; it
resets only on the next plugin reload.
Guards release.yml against non-tag refs and fixes the silent
ignore of body_path / tag_name that left every Gitea release
since v1.4.1 with an empty body.
The release-action@main reads GITHUB_REF directly and rejects anything
that doesn't start with refs/tags/. The previous workflow tried to work
around this by passing tag_name as an action input, but the action's
action.yml never declared tag_name (or body_path) - both inputs were
silently ignored, which is why every Gitea release since v1.4.1 was
published with an empty body.
Changes:
- New "Validate tag ref" step fails fast with a clear message when the
workflow is dispatched from a branch ref instead of a tag ref.
- workflow_dispatch.inputs.tag dropped; recovery now means picking the
tag from Gitea's Ref dropdown so GITHUB_REF lines up with refs/tags/.
- release-body.md is re-emitted as a step output and passed via body:
(the input the action actually reads) instead of body_path.
- tag_name input removed from the action call - the action derives the
tag from GITHUB_REF_NAME on its own.
Both workflows looked for "**Hellion Chat <version>" as the changelog
subblock header, but the yaml convention is "**v<version> — <subtitle>"
(matches verify-changelog-sync.sh and the slim-rule grep). Plus the
indent-strip was 2 spaces, but prettier writes the changelog block with
4-space indent. Both regressions silently failed every release-workflow
run since the format change — likely why v1.4.3 was released manually.
Sync header marker to "**v$version " and indent-strip to 4 spaces in
both files.
Gitea Actions reads exclusively from .gitea/workflows/, not from
.github/workflows/. Since the cutover in v1.4.3 only the security
workflow has been running — release and forge-announce silently sat in
the wrong directory and never fired on any tag push. v1.4.3 must have
been released manually.
Move build, release and forge-announce yamls to .gitea/workflows/. The
.github/forge-posts/ and .github/release-footer.md data files stay where
they are; the workflows reference them by repo-relative path and that
keeps working.
For the v1.4.4 backfill: workflow_dispatch via the Gitea web UI with
tag=v1.4.4 will run release.yml + forge-announce.yml against the tagged
tree (which doesn't contain this migration). The dispatch yaml itself
is read from the default branch, not the tag, so the missing yamls in
the v1.4.4 tag tree don't matter.
- IsAllowedForStorage warning now only fires for ChatTypes the build
doesn't recognise (Enum.IsDefined), not for opted-out known ones
- Drop stale tests-location comment in HonorificService
Pre-push grep-verification found four stale v1.4.3 mentions outside the
Slim-Rule history files:
- Plugin.cs schema-gate error message referenced v1.4.3 by name in both
the comment and the user-facing exception text. Schema stays at v16,
but the message now points at the current release
- README.md latest-release badge bumped to v1.4.4
- README.md version header bumped to v1.4.4
- README.md Project Status block rewritten for v1.4.4 with the threading
and IPC safety items as the lead
ROADMAP.md historical references to v1.4.3 are intentional (released-tag,
foundation-reference) and stay.
F3.2: a future FFXIV patch can introduce ChatTypes that aren't on any
existing whitelist, and the filter currently routes them silently
through the unknown-channel failsafe. Add a dedup HashSet (per runtime,
NonSerialized) so the first hit per ChatType logs a Warning. The
failsafe behaviour itself is unchanged — only visibility is new.
F3.1: future FFXIV patches can add new ChatTypes that aren't on any
existing whitelist. With the field defaulted to false a new install
would silently drop those channels until the user opts in. New configs
now start with PrivacyPersistUnknownChannels=true via a constant in
PrivacyDefaults. Existing configs keep their explicit choice — the
deserializer overrides the initializer, so no migration and no schema
bump.
F9.2: PreloadCache spawned a new Thread without IsBackground, which kept
the plugin unload blocked until the warmup finished (typically
100-300 ms). Setting IsBackground=true plus a named thread matches the
pattern already used in MessageManager (F6.1) and Plugin.RetentionSweep
(F9.3) since v1.4.0.
F4.1: replace the block threading comment with per-method banners that
read like documentation at the call site. F4.2: TryUnsubscribe now logs
Warning instead of Debug — a silent unsubscribe failure leaks a live
subscription across plugin reloads. F4.3: CurrentTitle gets a one-line
banner matching the same convention.
F2.1: ActiveTempTabCount was doing a LINQ Count under _tempTabsLock on
every read, including the hot-path HandleTell guard. Replace with an
Interlocked counter kept in sync with Config.Tabs from inside the
existing mutation paths (SpawnTempTab, DropOldestTempTab, OnLogout).
Initialize from the persisted Tabs list on Initialize() to handle
configs that already contain TempTabs from a prior session.
Plugin.cs SaveConfig snapshot-restore mutates Config.Tabs outside of
AutoTellTabsService; expose ResyncTempTabCounter() and call it after
AddRange so the counter stays consistent. Plugin.cs:168 crash-recovery
RemoveAll runs before Initialize() and is covered by the init snapshot.
yaml.changelog and repo.json.Changelog now use **vX.Y.Z** subblock
headers instead of the older **Hellion Chat X.Y.Z** form. Updated the
three regex patterns (yaml check, repo.json check, version counter)
and re-enabled Block C in preflight.sh — the SKIP workaround is no
longer needed.
Translate all remaining German sections in docs/CHANGELOG.md and
docs/ROADMAP.md to English for consistency across the repository.
Previously English sections left unchanged.
- SettingsOverview: replace dynamic key lookup via ResourceManager with
direct HellionStrings property access; switch static readonly array to
BuildCardDefs() method to ensure correct initialization order
- ThemeAndLayout: replace all ResourceManager.GetString calls with direct
HellionStrings/Language property access throughout DrawThemeSection()
and DrawChatColorsApplyBanner()
Also rework DE/EN string copy for a more natural, less formal tone in the German localization, and to better match the English source text. This includes
Changed HellionChat.yalm but need to Ajust the preflight script to not fail on this non-code change. TODO: Fix the script to only check for code changes in the future.
- 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.