G connects PayloadHandler to the runtime — this is the activation point
after E1-E6 built the type and F registered it in DI:
- StartAsync calls MessageList.AttachPayloadHandler(_payloadHandler) to
complete the §6.2 cycle-resolution (ctor-cycle was broken by setter,
this is where the setter actually fires)
- StartAsync registers AddonLifecycle listener for MoveTooltip on
PostUpdate of "ItemDetail" and "ActionDetail" addons
- StopAsync unregisters the listener
- Both Register/Unregister wrapped in Plugin.Framework.RunOnFrameworkThread
as defensive insurance — IAddonLifecycle thread-affinity is not
explicitly documented in Dalamud API; wrap keeps the v1.5.6 runtime
contract intact (per spec §5-G note)
Mirrors existing IpcManagerInitHostedService / TypingIpcInitHostedService
pattern in Infrastructure/Hosting/. PluginHostFactory adds the
AddHostedService<PayloadHandlerInitHostedService>() registration.
After G, the chunked-message-render pipeline is end-to-end functional:
MessageList renders via ChunkRenderer, _handler is wired so popups fire
on click/hover, MoveTooltip repositions native item-tooltips away from
the chat window.
H integrates the chunk-render pipeline into MessageList:
- Extends ctor to 4 params (themes, resolver, fonts, chunkRenderer);
TokenResolver preserved as load-bearing dep
- Adds private PayloadHandler? _handler field + internal
AttachPayloadHandler(PayloadHandler) setter
- Switches DrawCompactRow/DrawCardRow render-path to
_chunkRenderer.DrawChunks(message.Content, wrap, handler, 0f)
instead of plain TextUnformatted
Setter-injection for PayloadHandler is the §6.2 cycle-resolution
(PayloadHandler → MainWindow → MessageList → PayloadHandler ctor-cycle
broken by post-construction wiring). G's HostedService.StartAsync will
call AttachPayloadHandler after both singletons resolve.
Also extends MessageList DI-reg in PluginHostFactory.cs with the
ChunkRenderer arg (4th GetRequiredService).
F adds the 3 new DI registrations needed for the v1.7.1 R-Block:
- ChunkRenderer (4-param ctor: themes, fonts, logger, gameFunctions)
- PayloadHandler singleton (7-param ctor: themes, ipc, functions,
inputBar, mainWindow, chunkRenderer, logger)
- Lender<PayloadHandler> factory (closure over sp, constructs a fresh
PayloadHandler per Borrow() — used by InputPreview in Sub-Task I)
All three use Factory-Lambdas because Lender<T> has an internal ctor and
ChunkRenderer/PayloadHandler are internal sealed (ActivatorUtilities
can't reflect into internal ctors per [[reference_hellion_chat_di_container_v150]]).
MainWindow DI-reg update is deferred to Sub-Task A2 (split per Flo
2026-05-27 to avoid the DI-cycle that would otherwise emerge from the
PayloadHandler → MainWindow → MessageList → PayloadHandler graph —
cycle resolved via setter-injection on MessageList in G/H).
G is next: wires PayloadHandlerInitHostedService.StartAsync to register
the AddonLifecycle listener for MoveTooltip and call MessageList.
AttachPayloadHandler. H adds the MessageList ChunkRenderer ctor-param +
AttachPayloadHandler setter.
E6 closes out the PayloadHandler resurrection. MoveTooltip handles the
cross-viewport AddonLifecycle tooltip-repositioning logic — reads
MainWindow.LastViewport/LastWindowPos/LastWindowSize (from A1) to filter
events and reposition the native item tooltip away from the chat window.
Whole method marked `public unsafe void` per spec §4.2 (matches v1.5.6
exactly — avoids per-read unsafe-block scoping).
LogWarning added on the unexpected-AddonArgs early-out branch (wires
_logger into real use, prevents CS0414 unused-field warning).
PayloadHandler is now feature-complete. F registers it in DI; G wires
the AddonLifecycle.RegisterListener for MoveTooltip from a HostedService;
H adds MessageList.AttachPayloadHandler setter-injection.
E2 fills the popup-dispatch layer of PayloadHandler:
- DrawPopups: switch-dispatch over payload types, with TODO(E3)/(E4)
markers at the deferred Draw{Player,Item,Status,Uri}Popup call sites
- Integrations: invokes registered IPC integrations (LogWindow.Plugin.Ipc
-> _ipc substitution per §4.2)
- ContextFooter: ScreenshotMode + HideChat checkboxes (Plugin.Config
static-bridge substitutions per §4.2)
- StringifyMessage: pure helper, 1:1 from v1.5.6
Adds PopupSfx const (E1 polish — needed by E5's Click for
UIGlobals.PlaySoundEffect). Removes #pragma CS0169 for _popup since
DrawPopups now writes the field; the warning no longer triggers.
E3 will fill DrawPlayerPopup + FindCharacterForPayload; E4 the
Item/Status/Uri popups; E5 the Hover/Click bodies; E6 MoveTooltip.
Replaces C2's no-op WrapText stub with the full word-wrap pipeline
(WrapText / WrapEncodedLine / CalcWordWrap / DrawText / FindFirstSpace).
ChunkRenderer.DrawChunk's text-path now renders properly wrapped text
with payload hover-highlights and click-binding via PostPayload (which
was already full-ported in C2).
Also adds LastLink and PayloadBounds static fields that C2's PostPayload
port required but did not declare; DrawText needs both for per-segment
hover-rectangle accumulation across wrapped lines.
Unblocks E5's Hover paths that depend on functional WrapText for
status/item tooltip rendering. No structural changes — pure body
migration of the v1.5.6 unsafe word-wrap implementation.
Replaces the 15-LOC C2 forward-stub with the full PayloadHandler
skeleton. Class header flips to `internal sealed` per §4.1; ctor takes
7 DI-registered services (ThemeRegistry, IpcManager, GameFunctions,
InputBar, MainWindow, ChunkRenderer, ILogger) per Flo decision
2026-05-27 (ChunkRenderer was added to the ctor list to satisfy the
§4.2 _chunkRenderer.DrawChunks references in HoverStatus/HoverItem/
DrawItemPopup paths — those land in E2-E5).
Draw() per-frame popup tick is a 1:1 port from v1.5.6 PayloadHandler.
DrawPopups() call is stubbed as TODO(E2) since that method lands in E2.
Hover/Click signatures remain empty (E5 fills the bodies, but the
signatures must compile so ChunkRenderer + ImGuiUtil callers stay live).
Skeleton-only — DrawPopups/Integrations (E2), DrawPlayerPopup (E3),
DrawItemPopup/DrawStatusPopup (E4), Hover/Click bodies (E5),
MoveTooltip (E6) all defer to their respective sub-sub-tasks.
Completes the ChunkRenderer pipeline. DrawIcon is a 1:1 port of v1.5.6
ChatLogWindow.DrawIcon (GFD-icon font-relative rendering via
Plugin.TextureProvider + ImGuiUtil.PostPayload). C2's TODO(C3) stub in
DrawChunk's IconChunk branch is replaced with the real dispatch.
EmotePayload special-case wired via EmoteCache.GetEmote (static helper
per §6.8).
Also adds a one-line rationale comment for the surviving _logger discard
(C2 code-quality-review polish — discard kept because _logger is not yet
consumed; E-task wiring will likely add call-sites later).
Resurrects v1.5.6 ChatLogWindow's DrawChunks/DrawChunk text-rendering
pipeline into the new ChunkRenderer Components-Layer class. Text-chunk
path is the full v1.5.6 migration (Plugin.Config.ScreenshotMode,
_themes.Active.Colors.TextPrimary, _fonts.ItalicFont/_fonts.AxisItalic
substitutions applied per §4.2/§4.5); icon-chunk dispatch in DrawChunk
is stubbed pending C3 (EmoteCache + DrawIcon path).
ImGuiUtil.WrapText is forward-stubbed in Util/ImGuiUtil.cs as a no-op
TextUnformatted wrapper — Sub-Task D will replace the body with the
full ~220-LOC word-wrap pipeline. ImGuiUtil.PostPayload is also
forward-stubbed (payload hover/click routing belongs to Sub-Task E).
Both stubs are the cleanest cut to keep DrawChunk's body faithful to
v1.5.6 and avoid temporary fallback paths inside ChunkRenderer.
PayloadHandler.cs is a minimal forward-stub class (Hover + Click stubs
only) required by the DrawChunks/DrawChunk and PostPayload signatures.
Sub-Task E will replace this stub with the full implementation.
Discard pattern from C1 removed for _themes/_fonts (now genuinely
consumed by DrawChunks/DrawChunk); _logger discard kept — not yet
consumed in C2, deferred to E-task wiring.
Extracts v1.5.6's `ChatLogWindow.HidePlayerInString` / `HashPlayer` into
a standalone `Ui/Components/ChunkRenderer` class. C1 lands the skeleton
(ctor + DI-deps + salt + two pure helpers); C2 will add DrawChunks/DrawChunk
text-path; C3 will add DrawIcon + EmoteCache integration.
Salt is per-ctor random matching v1.5.6 session-random behavior — hashed
player names change every plugin reload to avoid stable cross-session
linkage (Spec §6.5 decision).
GameFunctions injected via ctor (not static Plugin.Functions) because
Plugin.Functions is a non-static internal property — injection is the
correct Components-layer pattern for this dependency.
Not yet DI-registered (Sub-Task F) and not yet consumed by MessageList
(Sub-Task H) — class compiles standalone.
Replaces the v1.5.6 `LogWindow.LastViewport/LastWindowPos/LastWindowSize`
window-instance state with public/internal MainWindow surfaces refreshed
at the top of Draw() each frame. PayloadHandler.MoveTooltip in Phase 2
will read these to filter cross-viewport AddonLifecycle events and to
reposition the native item tooltip away from the chat window.
LastViewport is `internal unsafe` (not public) — the only consumer is
PayloadHandler.MoveTooltip in the same assembly; keeping the raw pointer
out of the public surface is the safer default.
Split from Sub-Task A — Lender injection lives in A2 (after F's DI-reg).
Replaces v1.5.6's direct LogWindow.Chat mutation pattern with typed mutators
that LogWarning + clip/drop on BufferCapacity overflow (silent-overwrite
semantics preserved, but overflow is now observable via /xllog).
Plumbing for v1.7.1 PayloadHandler resurrection — DrawPlayerPopup (tell-
prefix) and DrawStatusPopup (status-link append) will call these mutators
instead of mutating a public field.
Adds persisted ScreenshotMode flag to Configuration (was previously a
ChatLogWindow-instance field in v1.5.6). Schema bump is additive — no
breaking change. SelfTest renamed V20→V21 with matching version-gate
flip and new touch-test for the new field.
Pre-flight for v1.7.1 PayloadHandler-Pipeline Resurrection.
Same shadowing-via-instance-property as Plugin.CurrentTab above. Comments
trimmed to default 1-3 line density; security/threading WHY-blocks earn
their lines when they document non-obvious invariants, not standard C#
name resolution.
Three review-pass fixes on SaveEditingBuffer:
- Wrap File.Move in try/catch that deletes the .tmp sibling on failure
(AV-scanner lock, EXDEV, share-violation) then rethrows so the outer
IOException catch still owns the error path. Avoids accumulating
'<slug>.json.tmp' litter in the themes dir on retry storms.
- Reduce PII in the five new LogWarning calls that previously included
full paths containing the user's home directory. Filename-only via
Path.GetFileName is sufficient for triage; the two forensics-critical
path-escape log calls keep full paths because diagnosing the escape
needs the resolved target. WHY-comment anchors the v1.8.0 PII
re-audit roadmap.
- Replace seven hardcoded 'ThemeRegistry.cs:<line>' references in
comments with method-name + symbol descriptions so future Switch/
RefreshCustomCache refactors do not bit-rot the comments.
Build 0/0, csharpier clean.