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`/`