diff --git a/crates/benilla-app/assets/ui/ScriptLogFrame.xml b/crates/benilla-app/assets/ui/ScriptLogFrame.xml index 375c1dcc..4d25fac9 100644 --- a/crates/benilla-app/assets/ui/ScriptLogFrame.xml +++ b/crates/benilla-app/assets/ui/ScriptLogFrame.xml @@ -18,6 +18,60 @@ -- A `seq`, not an index: indices shift when the cap evicts. BenillaScriptLogSelected = nil + -- Of the three kinds, two are things the player can act on: `error` (code raised) and + -- `load` (an addon is not running). A `warn` is a **note**: a call this client accepted + -- and quietly did nothing with, and almost every one is a document naming a frame, + -- template or CVar this installation has not got -- a TitanPanel plugin with no + -- TitanPanel, an `inherits=` on an optional dependency's template, OpenMapClassic + -- switching off a map CVar 1.12 never had. The reference's answer to each of those is a + -- severity-2 line in FrameXML.log or a raise inside the addon's own `pcall`, so the + -- player is shown none of them; counting them as problems made this window read as a + -- list of client faults when it was a list of addons the player does not own. They are + -- one checkbox away, and the log still carries every one. + BenillaScriptLogShowNotes = false + + -- Engine row indices, in order, for the rows this view shows. + BenillaScriptLogView = {} + + function BenillaScriptLog_IsNote(kind) + return kind == "warn" + end + + -- Rebuilds `BenillaScriptLogView`; returns the problem and note counts over every + -- retained row, shown or not. + function BenillaScriptLog_Rebuild() + local retained = BenillaGetNumScriptErrors() + local view, problems, notes = {}, 0, 0 + for i = 1, retained do + local _, kind = BenillaGetScriptErrorInfo(i) + if BenillaScriptLog_IsNote(kind) then + notes = notes + 1 + if BenillaScriptLogShowNotes then table.insert(view, i) end + else + problems = problems + 1 + table.insert(view, i) + end + end + BenillaScriptLogView = view + return problems, notes + end + + function BenillaScriptLog_SetShowNotes(show) + BenillaScriptLogShowNotes = (show and true) or false + -- A note read in the detail pane under a list that no longer lists it reads as a + -- stuck window, so turning them off lets the selection go with them. + if not BenillaScriptLogShowNotes and BenillaScriptLogSelected then + for i = 1, BenillaGetNumScriptErrors() do + local seq, kind = BenillaGetScriptErrorInfo(i) + if seq == BenillaScriptLogSelected and BenillaScriptLog_IsNote(kind) then + BenillaScriptLogSelected = nil + break + end + end + end + BenillaScriptLog_Update() + end + -- `strsub` counts bytes, so the cut backs up over UTF-8 continuation bytes (0x80..0xBF). function BenillaScriptLog_RowText(message) local cut = strfind(message, "\n", 1, 1) @@ -45,30 +99,46 @@ function BenillaScriptLog_Update() local shown, total = BenillaGetNumScriptErrors() + local problems, notes = BenillaScriptLog_Rebuild() + local rows = table.getn(BenillaScriptLogView) - -- "Problems", because two of the three kinds are not errors. + getglobal("BenillaScriptLogNotesCheck"):SetChecked(BenillaScriptLogShowNotes) + + -- "Problems", because `load` is not an error either; the notes are counted beside + -- them rather than among them, so the window never reports a TitanPanel plugin with + -- no TitanPanel as a fault of this client's. local summary - if total == 0 then - summary = "No problems this session." - elseif total > shown then - summary = format("%d problems (%d older not kept)", total, total - shown) - elseif total == 1 then + if problems == 1 then summary = "1 problem" + elseif problems > 0 then + summary = format("%d problems", problems) else - summary = format("%d problems", total) + summary = "No problems" + end + if notes == 1 then + summary = summary .. ", 1 note" + elseif notes > 0 then + summary = summary .. format(", %d notes", notes) + end + if problems == 0 and notes == 0 then + summary = "No problems this session." + end + if total > shown then + summary = summary .. format(" (%d older not kept)", total - shown) end getglobal("BenillaScriptLogSummary"):SetText(summary) local scrollFrame = getglobal("BenillaScriptLogListScrollFrame") - FauxScrollFrame_Update(scrollFrame, shown, BENILLA_SCRIPTLOG_ROWS, BENILLA_SCRIPTLOG_ROW_HEIGHT) + FauxScrollFrame_Update(scrollFrame, rows, BENILLA_SCRIPTLOG_ROWS, BENILLA_SCRIPTLOG_ROW_HEIGHT) BenillaScrollTrough_Follow("BenillaScriptLogListScrollFrame") local offset = FauxScrollFrame_GetOffset(scrollFrame) local selectedRow = nil for i = 1, BENILLA_SCRIPTLOG_ROWS do local button = getglobal("BenillaScriptLogRow" .. i) - local index = offset + i - local seq, kind, message, count = BenillaGetScriptErrorInfo(index) + local index = BenillaScriptLogView[offset + i] + local seq, kind, message, count + if index then seq, kind, message, count = BenillaGetScriptErrorInfo(index) end if seq then button.seq = seq button.message = message @@ -221,6 +291,22 @@ + + + + + + + local label = getglobal(self:GetName() .. "Text") + label:ClearAllPoints() + label:SetPoint("RIGHT", self, "LEFT", -2, 1) + label:SetText("Show notes") + + BenillaScriptLog_SetShowNotes(self:GetChecked()) + + + diff --git a/crates/benilla-ui/src/script/tooltip/verbs.rs b/crates/benilla-ui/src/script/tooltip/verbs.rs index 0b25dc34..c7bd06d1 100644 --- a/crates/benilla-ui/src/script/tooltip/verbs.rs +++ b/crates/benilla-ui/src/script/tooltip/verbs.rs @@ -488,6 +488,16 @@ mod tests { /// `.lua` and `.xml` found only valid `ANCHOR_*` strings, and the addon harness has never /// reproduced it. Whatever reaches it does so by a path source search cannot see, so the /// warning must identify itself. + /// + /// **It did.** The call site the warning printed is the realm's own FrameXML, not an addon, + /// which is why no addon scan could find it: `Shop_ProcessEntries` + /// (`Interface\FrameXML\Turtle_ShopUI\Turtle_ShopUI.lua:530`) runs + /// `ShopFrameScanTooltip:SetOwner(WorldFrame, "BOTTOMRIGHT")` once per shop entry before + /// `SetHyperlink`, to force each item into the cache. The anchor is dead weight there -- + /// the tooltip is a hidden scanner that is never positioned for a player to read -- so the + /// reference's own fallback to mode 0 / `ANCHOR_LEFT` is all the realm ever got from it + /// either, and the warning is a true report of sloppy realm code, not a gap here. The count + /// is one per entry in the shop list. #[test] fn an_unknown_anchor_warning_names_the_lua_line_that_passed_it() { let mut s = UiScript::new().unwrap(); diff --git a/docs/ADDON_COMPAT.md b/docs/ADDON_COMPAT.md index c96994ed..270446f7 100644 --- a/docs/ADDON_COMPAT.md +++ b/docs/ADDON_COMPAT.md @@ -4990,3 +4990,146 @@ counts are the whole argument: `GetHighlightColor` looked like the biggest gap at 39 sites and is not a widget method at all: every one of them is `DBB2:GetHighlightColor()`, DifficultBulletinBoard's own method on its own table. Counting call sites without checking the receiver is how that kind of row gets written. + +## 2026-11-06: the 34 problems in the script log were 34 addon packaging bugs + +A play session reported "a bunch of script errors … including 7 addon load failures", with a +screenshot of `/errors` reading **34 problems**, every visible row `BetterCharacterPanel/…`. The +window holds what the session recorded, so the first job was to read all 34 rather than the +thirteen the list shows. A capture over the player's own AddOns folder with an eval that walks +`BenillaGetScriptErrorInfo` printed every row: + +```sh +BENILLA_HOME=$SCRATCH/bhome WOW_CAPTURE=ui-spellbook WOW_CAPTURE_ADDONS=1 \ + WOW_CAPTURE_SAVED=C_Thun-Gromgosh WOW_CVAR=checkAddonVersion=0 \ + WOW_CAPTURE_EVAL=$SCRATCH/dump_errors.lua WOW_CAPTURE_OUT=/tmp/shot.png \ + cargo run -q --release -p benilla +``` + +41 rows offline (the extra seven are the no-server artifacts a capture always adds: `UnitFactionGroup` +nil in HCQuest, `GetXPExhaustion` nil in `ExhaustionTick`, a nil concat in TurtleMail, and the +combat-stats stand-in). **Not one of the 34 was a benilla defect.** The breakdown: + +| rows | cause | verdict | +| --- | --- | --- | +| 16 | `BCP_Inspect` parents to `InspectPaperDollFrame` and anchors 45 overlays to `Inspect*Slot` | `Blizzard_InspectUI` is `## LoadOnDemand: 1`, so none of those frames exists when BetterCharacterPanel loads. Faithful: the log line even says so (`is LoadOnDemand — not loaded`) | +| 7 | `.toc`/`