From fd7737829586ad7f33d91a65d93875aee12bdc49 Mon Sep 17 00:00:00 2001 From: Glyalith Date: Tue, 1 Sep 2026 21:39:21 -0600 Subject: [PATCH] Chat: keep the display bridge off windows Blizzard has not opened yet Moving a chat type to a new window threw a secret-value error at ChatFrameUtil.lua:711 and left the pop-out half done. Blizzard seeds the new window from the source frame inside its own loop -- GetMessageInfo, compare the line's accessID, AddMessage -- and our AddMessage post-hook tainted the rest of that loop, so the next GetMessageInfo answered with a secret accessID. Unopened chat frames now stay unbridged, and the first integrate pass after Blizzard opens one installs the bridge and backfills our window from its buffer. --- EllesmereUIChat/EllesmereUIChat_Engine.lua | 27 ++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/EllesmereUIChat/EllesmereUIChat_Engine.lua b/EllesmereUIChat/EllesmereUIChat_Engine.lua index 59da7a152..0812cb728 100644 --- a/EllesmereUIChat/EllesmereUIChat_Engine.lua +++ b/EllesmereUIChat/EllesmereUIChat_Engine.lua @@ -904,9 +904,24 @@ local function ChatFrameWheel(cf, delta) end end +-- Blizzard opens a new permanent window (ChatFrameUtil.PopOutChat) or a +-- temporary whisper window by taking one it considers unopened and seeding it +-- from the source frame inside its own copy loop: GetMessageInfo, compare the +-- line's accessID, AddMessage, next line. An AddMessage hook taints the rest of +-- that loop, so the NEXT GetMessageInfo answers with a secret accessID and the +-- compare throws (ChatFrameUtil.lua:711) with the pop-out half done. Unopened +-- frames therefore stay unbridged; the first integrate pass after Blizzard +-- opens one installs the bridge and backfills our window from its buffer. +local function IsChatFrameOpen(cf) + if cf.isTemporary then return cf.inUse == true end + local id = cf:GetID() + return id > 0 and FCF_IsChatWindowIndexActive(id) +end + local function InstallBridge(cf) local d = CFD(cf) - if d.bridged then return end + if d.bridged then return false end + if not IsChatFrameOpen(cf) then return false end d.bridged = true -- EngineTail's signature matches the hook's (self arrives as its cf); -- the trailing MessageFormatter argument is dropped by arity. @@ -921,6 +936,7 @@ local function InstallBridge(cf) end cf:SetScript("OnMouseWheel", ChatFrameWheel) cf:EnableMouseWheel(true) + return true end ------------------------------------------------------------------------------- @@ -1000,7 +1016,14 @@ local function IntegrateChatFrame(cf) RebuildWindowFromBuffer(cf) return end - InstallBridge(cf) + -- A frame bridged only now was opened since the last pass (a pop-out, a + -- reused temporary window): its lines were copied in behind our back. The + -- empty check keeps a late bridge on a window we already render (session + -- history replayed into it) from wiping what only exists on our side. + if InstallBridge(cf) and win.smf:GetNumMessages() == 0 then + RebuildWindowFromBuffer(cf) + return + end -- extraLines allowance, same as the bridge tail's check: replayed -- session-history lines exist only on our side, and the login full -- passes re-integrate every frame right after the restore -- without