Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Frostforge/Core/Core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local addonName, J = ...
-- Keep the addon folder and saved-variable keys stable for existing installs.
J.Brand = { name="Jiberish's Frostforge", shortName="Frostforge", website="https://theigloo.io" }
local Core = {
version = "0.9.12",
version = "0.9.13",
modules = {}, clients = {}, owned = {}, notices = {},
order = { "minimap", "playerFrame", "targetFrame", "focusFrame", "actionHub" },
settingsOrder = { "minimap", "playerFrame", "targetFrame", "focusFrame", "targetTargetFrame", "partyFrames", "actionHub" },
Expand Down Expand Up @@ -438,10 +438,14 @@ function Core:Command(input)
elseif command == "reset" then ok, message = J.ProfileManager:Reset(rest ~= "" and rest or nil)
elseif command == "export" then self:Print(J.ProfileManager:Export()); return
elseif command == "import" then ok, message = J.ProfileManager:Import(rest)
elseif command == "status" or command == "diagnostics" then self:Status(); return
elseif command == "partydebug" then J.SmallFrames:PartyDiagnostics(); return
elseif command == "status" or command == "diagnostics" then
if rest:lower()=="party" then J.SmallFrames:PartyDiagnostics() else self:Status() end
return
else
self:Print("/jui opens the movable options window. Phase 1: /jf theme paladin_ret | reloadtheme | debug [on|off] | status")
self:Print("/jf set <component> <property> <value> | show/hide <component> | reset [component] | export | import <backup>")
self:Print("/jf partydebug reports party-frame attachment and visibility details.")
self:Print("Components: " .. table.concat(self.settingsOrder,", "))
self:Print("Properties: " .. table.concat(self.propertyOrder,", "))
self:Print("Example: /jf set actionHub width 800")
Expand Down
68 changes: 68 additions & 0 deletions Frostforge/Core/SmallFrames.lua
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,71 @@ function S:Tick()
else self.status[key]="off" end
end
end

-- On-demand only: report each visibility gate without refreshing, mutating
-- provider frames or formatting secret values (including error payloads).
local function diagnosticValue(ok,value)
if not ok then return "error" end
if not J.Core:IsSafe(value) then return "restricted" end
if value==nil then return "none" end
if value==true then return "yes" end
if value==false then return "no" end
if J.Core:IsNumber(value) then return string.format("%.3g",value) end
if type(value)=="string" and (value=="player" or value:match("^party[1-4]$") or value:match("^raid%d%d?$")) then return value end
return "unavailable"
end
local function diagnosticCall(fn,...)
return diagnosticValue(pcall(fn,...))
end
local function diagnosticRead(frame,method,...)
if not usable(frame) then return "unavailable" end
if type(frame[method])~="function" then return "unsupported" end
return diagnosticCall(frame[method],frame,...)
end
local function diagnosticFrame(prefix,frame)
J.Core:Print(prefix..": gate="..diagnosticCall(J.SmallFrameAnchors.Visible,J.SmallFrameAnchors,frame)
.." | visible="..diagnosticRead(frame,"IsVisible")
.." | effectiveAlpha="..diagnosticRead(frame,"GetEffectiveAlpha")
.." | alpha="..diagnosticRead(frame,"GetAlpha"))
end
function S:PartyDiagnostics()
J.Core:Print("Party diagnostics "..J.Core.version.." | combat="..diagnosticCall(InCombatLockdown))
J.Core:Print("Current reads; attached/eligible/artwork describe the last update. Restricted values are not printed.")
local group=self.groups.partyFrames or {}
local config=group.config or J.ThemeManager:Read("partyFrames")
for _,kind in ipairs({"border","portrait"}) do
local enabled=kind=="border" and config.unitFrameShown or kind=="portrait" and config.shown
local source=kind=="border" and config.unitFrameSource or config.portraitSource
local state=group[kind]
local candidates=state and state.candidates or {}
J.Core:Print(kind..": "..(enabled and "on" or "off").." | requested="..source
.." | provider="..(state and state.provider or "unresolved").." | candidates="..#candidates)
if enabled then
if #candidates==0 then J.Core:Print(kind..": no cached party frames; attachment may be waiting for a group or combat to end.") end
for i,candidate in ipairs(candidates) do
local prefix=kind.." #"..i
local ok=pcall(function()
local unitOK,unit=pcall(J.SmallFrameAnchors.Unit,J.SmallFrameAnchors,candidate)
local unitLabel=diagnosticValue(unitOK,unit)
if not unitOK then unit=nil end
local present=unit and diagnosticCall(J.Portraits.HasToken,J.Portraits,unit) or "no"
local module=state.records[candidate.root]
J.Core:Print(prefix.." unit="..unitLabel
.." | attribute="..diagnosticRead(candidate.root,"GetAttribute","unit")
.." | exists="..(unit and diagnosticCall(UnitExists,unit) or "not checked")
.." | present="..present)
J.Core:Print(prefix.." attached="..diagnosticValue(true,module~=nil and module.active==true)
.." | eligible="..diagnosticValue(true,module~=nil and module.nativeVisible==true)
.." | artwork="..(module and (module.assetOK and "ready" or "unavailable") or "not fitted")
.." | shown="..diagnosticRead(module and module.frame,"IsShown")
.." | protected="..diagnosticRead(module and module.frame,"IsProtected"))
diagnosticFrame(prefix.." root",candidate.root)
local part=kind=="portrait" and candidate.portrait and candidate.portrait.region or candidate.health
diagnosticFrame(prefix..(kind=="portrait" and " portrait" or " health"),part)
end)
if not ok then J.Core:Print(prefix..": diagnostic read failed; continuing with the next frame.") end
end
end
end
J.Core:Print("End party diagnostics.")
end
2 changes: 1 addition & 1 deletion Frostforge/Frostforge.toc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## X-Website: https://theigloo.io
## Notes: Optional sculpted unit-frame skins and fantasy portrait surrounds with Blinkii, mMediaTag, ElvUI and EllesmereUI support, matching cast-bar borders, action hubs and minimap artwork. In-game validation pending.
## Author: Jiberish
## Version: 0.9.12
## Version: 0.9.13
## SavedVariables: JiberishUIDB
## SavedVariablesPerCharacter: JiberishUICharacterDB
## OptionalDeps: LibSharedMedia-3.0, Blizzard_UnitFrame, Blizzard_ActionBar, Blizzard_Minimap, Blizzard_GameMenu, Blinkiis_Portraits, ElvUI, ElvUI_mMediaTag, EllesmereUI, EllesmereUIUnitFrames, EllesmereUIRaidFrames, EllesmereUIResourceBars, EllesmereUIActionBars, EllesmereUIMinimap
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.9.13

- Add `/jf partydebug` (also `/jf status party`) to report each party frame's attachment, unit assignment and visibility/opacity checks. It handles unavailable or restricted reads without requiring a pasted script. This diagnostic update does not change rendering; the remaining EllesmereUI party-border issue is still under investigation.

## 0.9.12

- Fix missing EllesmereUI party-member borders and portraits when native range fading uses protected opacity. Keep provider fading, Frostforge opacity and party sorting intact.
Expand Down
2 changes: 2 additions & 0 deletions docs/maintainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Upload the appropriate client ZIP from `dist/`. Each contains one `Frostforge/`

Preserve saved settings, combat deferral and provider ownership. Decorations must not capture input or change gameplay. Keep changes scoped; report automated checks separately from [in-game checks](VALIDATION.md).

For missing party borders, a player can run `/jf partydebug` while grouped and share the output. It reports cached attachments and current visibility checks without changing frames, printing character names or exposing restricted values.

## Artwork and records

[Current source records](ARTWORK.md) describe the retained art and manifests. Active builders are in `tools/`; run only the builder for the asset being changed, then check its hashes and fitting. Full source artwork and its original history are retained in the separate artwork library and migration backup, not in addon Git history. See the artwork guide for source-builder and optional audit instructions.
Expand Down
97 changes: 97 additions & 0 deletions tests/small_frames.lua
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,103 @@ test("retired party attachments reuse a bounded frame pool after repeated roster
assert(#M.objects==objects and not next(J.Core.notices))
end)

local function partyReport(M,J,command)
M.messages={}
local writes,objects=M.writes,#M.objects
local profile=J.ProfileManager:Export()
J.Core:Command(command or "partydebug")
local text=table.concat(M.messages,"\n")
assert(text:find("Party diagnostics "..J.Core.version,1,true) and text:find("End party diagnostics.",1,true),text)
assert(M.writes==writes and #M.objects==objects and J.ProfileManager:Export()==profile,"Diagnostic mutated UI or settings")
return text
end

test("party diagnostic always explains disabled and not-yet-attached states",function(M)
local J=M.load()
assert(partyReport(M,J):find("border: off",1,true))
enable(J,"partyFrames","ELLESMERE")
local text=partyReport(M,J,"status party")
assert(text:find("border: on | requested=ELLESMERE | provider=EllesmereUI | candidates=0",1,true))
assert(text:find("no cached party frames",1,true) and not next(J.Core.notices))
end)

for _,interface in ipairs({120100,16001}) do
for _,source in ipairs({"BLIZZARD","ELVUI","ELLESMERE"}) do
test("party diagnostic reads each "..source.." border and portrait without mutation "..interface,function(M)
local J=M.load({interface=interface});fixture(M,source);enable(J,"partyFrames",source,true)
M.combat=true
local text=partyReport(M,J,"diagnostics party")
assert(text:find("combat=yes",1,true))
for _,kind in ipairs({"border","portrait"}) do
for i=1,2 do
local prefix=kind.." #"..i
assert(text:find(prefix.." unit=party",1,true))
assert(text:find(prefix.." attached=yes | eligible=yes | artwork=ready | shown=yes",1,true))
assert(text:find(prefix.." root: gate=yes | visible=yes | effectiveAlpha=1 | alpha=1",1,true))
end
end
assert(not next(J.Core.notices))
end)
end
end

test("party diagnostic separates unreadable effective alpha from secret alpha without stopping",function(M)
local J=M.load({interface=16001});local roots=fixture(M,"ELLESMERE")
local self=member(M,"ELLESMERE","player",6)
for _,root in ipairs(roots) do
root.GetEffectiveAlpha=function() error(M.secret) end
root.GetAlpha=function() return M.secret end
end
enable(J,"partyFrames","ELLESMERE")
assert(J.SmallFrames.status.partyFrames:find("1 visible / 3 attached",1,true))
local text=partyReport(M,J)
for i=1,2 do
assert(text:find("border #"..i.." attached=yes | eligible=no | artwork=ready | shown=no",1,true))
assert(text:find("border #"..i.." root: gate=no | visible=yes | effectiveAlpha=error | alpha=restricted",1,true))
assert(text:find("border #"..i.." health: gate=no | visible=yes | effectiveAlpha=error | alpha=1",1,true))
end
assert(text:find("border #3 unit=player",1,true) and record(J,"partyFrames",self).frame.shown)
assert(not next(J.Core.notices))
end)

test("party diagnostic distinguishes restricted and failed identity reads and continues to other frames",function(M)
local J=M.load();local roots=fixture(M,"ELLESMERE");enable(J,"partyFrames","ELLESMERE")
roots[1].GetAttribute=function() return M.secret end
roots[1].IsVisible=function() return M.secret end
roots[1].secretAlpha=true
roots[2].GetAttribute=function() error(M.secret) end
M.tick(J.Core)
local text=partyReport(M,J)
assert(text:find("border #1 unit=none | attribute=restricted | exists=not checked | present=no",1,true))
assert(text:find("border #1 root: gate=no | visible=restricted | effectiveAlpha=restricted",1,true))
assert(text:find("border #2 unit=none | attribute=error",1,true))
assert(not text:find("diagnostic read failed",1,true) and not next(J.Core.notices))
end)

test("party diagnostic reports removed units, zero alpha and forbidden cached frames",function(M)
local J=M.load();local roots=fixture(M,"ELLESMERE");enable(J,"partyFrames","ELLESMERE")
M.unitData.party2=nil;roots[1].alpha=0
M.combat=true;M.tick(J.Core)
roots[2].forbidden=true
local text=partyReport(M,J)
assert(text:find("border #1 unit=party2 | attribute=party2 | exists=no | present=no",1,true))
assert(text:find("border #1 root: gate=no | visible=yes | effectiveAlpha=0 | alpha=0",1,true))
assert(text:find("border #2 root: gate=no | visible=unavailable | effectiveAlpha=unavailable",1,true))
assert(not next(J.Core.notices))
end)

test("party diagnostic never treats restricted existence as an absent member",function(M)
local J=M.load();local roots=fixture(M,"ELLESMERE");enable(J,"partyFrames","ELLESMERE")
local original=UnitExists
UnitExists=function(unit) if unit=="party2" then return M.secret end;return original(unit) end
roots[1].secretAlpha=true;M.tick(J.Core)
local text=partyReport(M,J)
assert(text:find("border #1 unit=party2 | attribute=party2 | exists=restricted | present=yes",1,true))
assert(text:find("border #1 attached=yes | eligible=yes | artwork=ready | shown=yes",1,true))
assert(text:find("border #1 root: gate=yes | visible=yes | effectiveAlpha=restricted",1,true))
assert(not next(J.Core.notices))
end)

test("party profile switches and sanitation preserve separate settings",function(M)
local J=M.load();local root=fixture(M,"ELVUI")[1];enable(J,"partyFrames","ELVUI",true)
J.ProfileManager:Set("partyFrames","smallFrameArt","RACE_TAUREN")
Expand Down
Loading