# Changelog

## [v0.3.4] (2026-05-22) - TBC `/camp` `/exit` `/logout` Taint Fix

### Bug Fixes

- **`/camp`, `/exit`, `/logout` typed in chat tripped `ADDON_ACTION_FORBIDDEN` on TBC / Anniversary 1.15.x with NamePrefix active** — v0.3.3 replaced the per-editbox `OnEnterPressed` script slot with an addon-Lua closure that invoked the captured original script via `securecall(origScript, editBox, ...)`. The `securecall` boundary clears taint at its call site, but on TBC / Anniversary the slash-dispatch chain (`ChatFrameEditBoxMixin:OnEnterPressed` → `SendText` → `ParseText` → slash-command dispatcher → protected `Logout()`) still failed the secure-execution check for `Logout()` — confirmed in the field after v0.3.3 shipped, with the failing trace `[TOGTools/Modules/NamePrefix/NamePrefix.lua]:161 → [C]: securecall → ChatFrameEditBox.lua:370 → SendText:252 → ParseText:207 → SlashCommands.lua:748 → Logout()`. The OnEnterPressed slot itself becomes addon-owned once `SetScript` runs on it, so the C-level taint check sees addon ownership on the dispatch path regardless of the `securecall` clear at the boundary. Switched the Classic / older-Retail hook from `SetScript("OnEnterPressed", ...) + securecall(origScript)` to `HookScript("OnKeyDown", ...)` keyed on `key == "ENTER"` or `"NUMPADENTER"`. `OnKeyDown` fires in its own C dispatch frame, ahead of `OnEnterPressed`, which is dispatched as a separate C-level call — our hook applies the prefix via `SetText` and returns to C *before* `OnEnterPressed` begins, so addon Lua is never on the call stack when `SendText` / `ParseText` / `Logout()` run. The `OnEnterPressed` script slot is no longer touched at all and stays bound to the original Blizzard handler, so the entire slash-dispatch chain executes in a fully secure context. `HookScript` chains alongside any existing `OnKeyDown` handler instead of claiming the slot; chat editboxes have no default `OnKeyDown` binding, so our hook is the only one on the editbox. The retail 12.0+ `EventRegistry "ChatFrame.OnEditBoxPreSendText"` path is unchanged. The `ApplyPrefix` leading-`/` early-exit is unchanged and continues to ensure no `SetText` is issued for slash commands even though the OnKeyDown hook still runs. Updated the file-header comment block to document the rejected `SetScript + securecall` approach alongside the previously-rejected `SendChatMessage` / `ChatEdit_SendText` / mixin-method-replace wraps, and updated the `ModifyMessage` doc comment to reference the new OnKeyDown entry point. Removed the v0.3.3 temporary `OnKeyDown` diagnostic since its purpose (verify `OnKeyDown` fires on TBC ahead of `OnEnterPressed`) is now load-bearing in production code. Location: `Modules/NamePrefix/NamePrefix.lua`.

---

## [v0.3.3] (2026-05-18) - NamePrefix /say Channel + TBC /logout Taint Fix

### Bug Fixes

- **NamePrefix did not fire on TBC / Anniversary 1.15.x in the v0.3.2 working tree** — During pre-release work toward this version the Classic hook was experimentally rewritten twice — first to wrap the global `SendChatMessage`, then to wrap the global `ChatEdit_SendText` (Name2Chat's pattern). Both wraps installed cleanly (verified with a temporary install-time `print`) but neither fire-time path was ever entered for user-typed chat on TBC / Anniversary 1.15.x — confirmed empirically with a temporary fire-time `print` inside `ModifyMessage`. On those clients `ChatFrameEditBoxMixin:OnEnterPressed` dispatches via `self:SendText()` using a captured local reference, so addon-level global reassignment never intercepts user chat. On Classic Era 1.15.x the global `ChatEdit_SendText` path is still active and did fire during testing, which initially masked the TBC/Anniversary regression. Restored v0.3.2's per-editbox `SetScript("OnEnterPressed", ...)` wrap that walks `ChatFrame1EditBox..ChatFrameN.EditBox` via `NUM_CHAT_WINDOWS` — this is the only entry point that reliably fires on every Classic flavor. Verified empirically on TBC (Anniversary, `Wowhead Looter` interface 20505) and Classic Era (interface 11508). Location: `Modules/NamePrefix/NamePrefix.lua`.
- **Per-editbox `OnEnterPressed` wrap tripped `ADDON_ACTION_FORBIDDEN` on `/logout`** — `SetScript("OnEnterPressed", addonFn)` leaves an addon-Lua handler on the C call stack whenever Enter is pressed in a chat editbox. When the user types `/logout`, the original `OnEnterPressed` we invoke from inside our handler runs through `ChatEdit_SendText` → `ChatEdit_ParseText` → slash-command dispatcher → protected `Logout()`. Because addon Lua is still on the stack, taint propagates the whole way and the secure-execution layer blocks `Logout()` on TBC and Anniversary. Fix: invoke the captured original script via `securecall(origScript, editBox, ...)` instead of a direct call. `securecall` is the canonical taint-clearing dispatcher (see WoWWiki "Secure Execution and Tainting") — it saves and clears the current taint flag around the call so `ParseText` → slash-handler → `Logout()` runs untainted regardless of what sits on the C stack above. Belt-and-suspenders: `ApplyPrefix` already early-exits on a leading `/`, so the editbox text is never modified for slash commands and its `GetText` result stays untainted on the secure dispatch path. Location: `Modules/NamePrefix/NamePrefix.lua`.

### New Features

- **NamePrefix `Say (/s)` channel toggle** — New checkbox at the top of the NamePrefix tab's Active Channels list. When enabled, the configured nickname is prepended to outgoing `/say` messages alongside the existing guild / officer / party / raid / instance toggles. `SAY` case added to the `ApplyPrefix` chatType dispatch table; `say = false` added to `DB_DEFAULTS.global.namePrefix`. Location: `Modules/NamePrefix/NamePrefix.lua`, `GUI/NamePrefixTab.lua`, `TOGTools.lua`.
- **Account-wide debug flag + `addon:Debug()` helper** — New `Debug` section in the Settings panel (ESC → Options → Addons → TOG Tools, or right-click the minimap button) with a `Verbose debug output` toggle. Stored at `db.global.debug`, default `false`. `addon:Debug(msg)` prints `|cffFF8000TOGTools|r <msg>` only when the flag is true; modules call it for hook-install confirmations and fire-time diagnostics that should be silent in normal play. NamePrefix uses it for two diagnostics: the install confirmation `NamePrefix: hook installed (OnEnterPressed xN)` (one-shot at addon load — requires `/reload` to re-fire after toggling the flag) and the per-send fire line `NamePrefix fire: chatType=X prefixed=true/false` (no message echo, takes effect on the next chat send without `/reload`). Location: `TOGTools.lua`, `GUI/Settings.lua`, `Modules/NamePrefix/NamePrefix.lua`.

### Improvements

- **NamePrefix channel defaults are now all `false`** — `say`, `guild`, `officer`, `party`, `raid`, `instance` all default to `false` in `DB_DEFAULTS.global.namePrefix`. Previously `guild` and `officer` defaulted to `true`. Fresh installs now opt-in per channel; existing saved settings are unaffected because AceDB merges defaults without overwriting saved keys. Location: `TOGTools.lua`.
- **`.luarc.json` / `TOGTools.code-workspace`** — Added `securecall`, `rawget`, `rawset` to `diagnostics.globals` for Lua LSP coverage of the new hook code and the v0.3.2 account-wide migration block.

---

## [v0.3.2] (2026-05-17) - NamePrefix BCC / Anniversary Fix

### Bug Fixes

- **NamePrefix did nothing on BCC and Anniversary (Classic Era 1.15.x)** — v0.3.0 relocated the Classic hook from `ChatEdit_SendText` to the global `ChatEdit_OnEnterPressed` to avoid tainting OPie's `securecall(ChatEdit_SendText, ...)` macrotext dispatch. On modern Classic builds (BCC, Anniversary 1.15.x) the chat editbox's `OnEnterPressed` script is the `ChatFrameEditBoxMixin:OnEnterPressed` method, NOT the global — so the wrapped global never fired and outgoing messages went un-prefixed. Replaced the global-wrap with a per-editbox `SetScript("OnEnterPressed", ...)` wrap that walks `ChatFrame1EditBox..ChatFrameN EditBox` (`NUM_CHAT_WINDOWS`) and prepends `ModifyMessage` to each editbox's existing script. This catches both the legacy `ChatEdit_OnEnterPressed` script binding and the modern mixin-method binding without touching any global function reference. Verified safe for OPie: the wrap only touches user-visible `ChatFrameN` editboxes, not OPie's synthetic Rewire editboxes, and never replaces `ChatEdit_SendText` or `ChatFrameEditBoxMixin.SendText`. Confirmed against Name2Chat's own analysis that `EventRegistry "ChatFrame.OnEditBoxPreSendText"` is **not** fired on Classic 1.15.x despite `EventRegistry` being backported — the existing `gv.isRetail120Plus` gate stays correct. Location: `Modules/NamePrefix/NamePrefix.lua`.
- **`.luarc.json`** — Added `NUM_CHAT_WINDOWS` to `diagnostics.globals`.

### Improvements

- **MainWindow remembers last-selected tab** — Opening the main window (minimap button, `/togt`, or `Toggle()` with no arg) now restores the tab that was active the last time the user closed the window. Stored as `db.char.lastTab`; per-character so different alts can have different defaults. Resolution order in `MainWindow:Open(tabKey)` is: explicit caller arg → `db.char.lastTab` (if the module still exists) → alphabetically first tab. Slash commands that pass an explicit tab (`/togt np`, `/togt mb`, etc.) still win, and clicking a different tab updates the saved value via `OnGroupSelected`. Location: `GUI/MainWindow.lua`, `TOGTools.lua`.
- **NamePrefix is now account-wide** — Moved `namePrefix` from `DB_DEFAULTS.char` to `DB_DEFAULTS.global` so the nickname, format string, per-channel toggles, and `hideIfCharName` are configured once and shared across every alt. Toons that shouldn't self-prefix rely on `hideIfCharName` (now defaulting to `true`) to suppress the prefix when the nickname matches the character's own name. One-shot migration in `Ace:OnInitialize` copies any pre-upgrade `db.char.namePrefix` with a non-empty nickname into `db.global.namePrefix` the first time an upgraded character logs in (only if the global table is still at defaults); subsequent alts inherit the now-global config. Leftover per-char entries are left in place since AceDB ignores keys not in the defaults. Location: `TOGTools.lua`, `Modules/NamePrefix/NamePrefix.lua`, `GUI/NamePrefixTab.lua`.
- **NamePrefix default** — `hideIfCharName` now defaults to `true` in `DB_DEFAULTS.global.namePrefix`. New accounts no longer self-prefix on the character whose name matches the configured nickname (the common case where the nickname IS the main's name). Existing saved-variables are unaffected. Location: `TOGTools.lua`.

---

## [v0.3.1] (2026-05-06) - TBC Compatibility Fix

### Bug Fixes

- **Addon Load tab crashed on TBC (and Wrath/Cata/Mists/Retail)** — `GetNumAddOns`, `GetAddOnInfo`, `IsAddOnLoaded`, `GetAddOnMemoryUsage`, and `UpdateAddOnMemoryUsage` were called as bare globals, but TBC+ moved them all into `C_AddOns.*`. Added five compat locals at the top of the module that prefer `C_AddOns.*` when available and fall back to the bare globals for Classic Era, matching the pattern already used elsewhere in the addon. Location: `Modules/AddonLoad/AddonLoad.lua`.

---

## [v0.3.0] (2026-05-04) - Mailbox Stale Watcher

### New Features

- **Mailbox Stale Watcher** — New tab (`/togt mail` / `mb` / `mailbox`) tracking mail expiration across every alt on the account so mail never gets auto-deleted at the 30-day mark for an unvisited character. Captures a per-alt inbox snapshot on each `MAIL_INBOX_UPDATE` event, stamping absolute `expiresAt = GetServerTime() + (daysLeft * 86400)` per item so the time-remaining math stays accurate even when the alt does not re-visit a mailbox for several days (same content-derived-timestamp pattern TOGPM uses). Login chat alert prints once when any alt has mail expiring within the configured threshold (default 2 days, slider-configurable 1–7 in the tab). Tab lists each alt with a row-per-mail breakdown of expiring items (sender, subject, time remaining), snapshot age display, and a `- stale` flag for snapshots older than 7 days. Live-refresh on `MAIL_INBOX_UPDATE` while the tab is active, so opening a mailbox in-game while the Mailbox tab is showing updates the rows in real time without forcing a manual tab-switch (`addon.MainWindow:Refresh()` is called from the engine's event handler when `addon.MainWindow.activeTab == "mailbox"`). Account-wide storage in `db.global.mailbox.snapshots[Name-Realm]` so every alt's data is visible from any character, full `Name-Realm` keys throughout to prevent collisions across connected-realm clusters. Location: `Modules/Mailbox/Mailbox.lua`, `GUI/MailboxTab.lua`.

### Improvements

- **AceDB schema** — Added `global` namespace to `DB_DEFAULTS` with `mailbox = { thresholdDays, snapshots }`. Account-wide scope so cross-alt mail data persists across every physical realm in a connected-realm cluster (per-`db.realm` would silo each physical realm of the cluster into a separate notes table — wrong for our use case). Location: `TOGTools.lua`.
- **Slash commands** — `/togt mb` / `/togt mail` / `/togt mailbox` open the Mailbox tab directly; help output updated. Location: `SlashCommands.lua`.
- **`.luarc.json`** — Added `GetInboxNumItems`, `GetInboxHeaderInfo`, and `ChatEdit_OnEnterPressed` to `diagnostics.globals` so the new mail API calls and the relocated NamePrefix hook target resolve cleanly under the Lua LSP.
- **TOC files** — All six TOC variants (Vanilla, BCC, Wrath, Cata, Mists, Mainline) updated to load `Modules\Mailbox\Mailbox.lua` and `GUI\MailboxTab.lua` after the AddonLoad pair.

### Bug Fixes

- **NamePrefix broke OPie macrotext ring entries (custom mounts, `/cast` macros)** — On Classic clients, NamePrefix wrapped the global `ChatEdit_SendText` to enable pre-send text modification. OPie's `Libs/ActionBook/Rewire.lua` dispatches each line of macro ring entries via `securecall(ChatEdit_SendText, box, false)`. Replacing the global with a wrapper created in our (insecure) addon loading context tainted that function reference, so OPie's secure dispatch path picked up taint when calling it — failing for any ring slot delivered as macrotext (custom mount macros, `/cast` slots), while direct spell-cast slots that bypass macrotext (most tradeskills) kept working. Fix: relocated the Classic hook target from `ChatEdit_SendText` to `ChatEdit_OnEnterPressed`, which is upstream of `ChatEdit_SendText` in the user-typed Enter-key flow but is *not* on OPie's Rewire dispatch path. The user-facing prefixing behavior is unchanged. The replaced-global pattern itself stays (`hooksecurefunc` fires post-call, so it can't be used to modify text before send); we just no longer taint the function OPie depends on. Location: `Modules/NamePrefix/NamePrefix.lua`.

---

## [v0.2.0] (2026-05-04) - Addon Load Monitor

### New Features

- **Addon Load Monitor** — New tab (`/togt al`) showing every installed addon's memory usage, load status, and load timing. Sortable by name, memory, load time, and status (failures-first when descending). Summary bar shows totals (loaded / failed / disabled / on-demand) and total memory. Requires the `!TOGT` companion addon for full timing coverage across all addons. Location: `Modules/AddonLoad/AddonLoad.lua`, `GUI/AddonLoadTab.lua`.
- **!TOGT companion integration** — `!TOGT` (companion addon) added as a required dependency. Loads before all other addons at the very start of the loading screen (the `!` prefix sorts before all letters) and records load order and per-addon offset into the `TOGToolsEarlyData` global. The Addon Load Monitor reads it at display time and shows a status-bar indicator (`!TOGT active` / `!TOGT missing`).
- **Shared `addon.RowList` component** — New file `GUI/RowList.lua`, ported and trimmed from FastGuildInvite's `GUI/RowList.lua`. Reusable sortable-row list for any tab that needs a tabular data view: brand-colored clickable header bar, click-toggle ASC/DESC sort with per-column `sortKey`/`sortDescDefault`/`sortable`/`gapBefore` opts and tie-break on `entry.name`, alternating row banding, virtual-scroll pool that grows with the parent's height, custom Blizzard-textured scrollbar, mouse-wheel scroll. Cells use `SetWordWrap(false)` + `SetMaxLines(1)` and anchor `LEFT/RIGHT` to the parent so column layouts compact elastically when the user shrinks the window — no row-wrap. Public API: `:New(parent, opts)`, `:SetData(arr)`, `:SetSort(key, desc)`, `:Refresh()`, `:Detach()`. Registered to all six TOC files after `Compat.lua`, before `MainWindow.lua`.
- **`Compat.lua` shared GUI helpers** — `addon.Tooltip.Owner` / `AnchorFrame` (smart anchor flipping based on screen half), `addon.AceGUIFrameScripts` (leak-safe raw frame scripts that restore prior handlers on pooled-widget release), `addon.GUI.AttachTooltip`, `addon.GUI.MakeColumnHeader`, `addon.GUI.ApplyMinResize`, `addon.GUI.DetachPool`. Used by every tab.

### Bug Fixes

- **Load times reporting `0.000s`** — `GetTime()` is frame-locked, so every `ADDON_LOADED` event fired in the same loading-screen frame returned the same timestamp. Per-addon deltas collapsed to zero. Switched the timing capture in `!TOGT.lua` and the fallback capture in `Modules/AddonLoad/AddonLoad.lua` to `debugprofilestop()`, which has sub-millisecond precision. Offsets are stored in seconds (ms / 1000) so downstream consumers are unchanged.
- **Load Time column sort produced random-looking order** — The sort comparator in `GUI/AddonLoadTab.lua` was reading `row.offset` (cumulative seconds since the first event) instead of `row.loadTime` (per-addon delta). Because `offset` is monotonic with load order, clicking the header effectively sorted by load order regardless of direction. Comparator now reads `row.loadTime`.
- **Addon Load tab wrapped to multiple lines on horizontal shrink** — The tab used AceGUI Flow with `widget:SetWidth(...)` per cell, so when the user shrunk the window narrower than the column-width sum AceGUI moved cells to a new line and the layout fell apart. Replaced the row-rendering path with `addon.RowList`, whose anchor-based cells truncate instead of wrapping. The window's `minWidth` was tightened from 640 to 520 now that compaction is graceful.
- **Tab status bar leaked between tabs** — `MainWindow:DrawTab` now resets the status text to the version string before delegating to the tab module, so a tab that overrides the status (e.g. Addon Load showing `!TOGT active/missing`) doesn't bleed that text into the next tab the user opens.
- **Tab content didn't reflow on tab switch** — `MainWindow:ApplyTabSize` now calls `f:DoLayout()` after sizing, so when switching between tabs whose `WINDOW_SIZE` differs, the new tab's content reflows immediately instead of inheriting stale layout.

### Improvements

- **Addon Load tab column widths tightened** — Status reduced from 175 → 85 px (fits "Wrong Version"); Memory reduced from 110 → 65 px (fits "999.9 MB"); Load Time reduced from 110 → 60 px. The auto-width Addon Name column reclaims the freed space. `RowList`'s `SCROLLBAR_GUTTER` reduced from `SCROLLBAR_WIDTH + 6` to `SCROLLBAR_WIDTH + 2` so the rightmost column sits ~5 px from the scrollbar. Status column gets `gapBefore = 5` to break the visual collision between right-justified Load Time text and left-justified Status text.
- **Sort indicator removed from active column header** — `RowList:_updateHeaderText` no longer appends a glyph (`v` / `^`) to the active sort column; WoW's default font doesn't render unicode triangles cleanly and ASCII letters look like typos. Click affordance is communicated via the column tooltip.
- **AceGUI pool-bleed protection** — `RowList:Detach()` orphans the row pool, header, and scrollbar to `UIParent` on host-widget release; a `_detached` guard on `:Refresh()` makes sure a recycled host doesn't trigger a pool grow that would re-attach rows into the next addon's widget. Wired via `body:SetCallback("OnRelease", ...)` in `GUI/AddonLoadTab.lua`.
- **`.luarc.json` updated** — Added `debugprofilestop` to `diagnostics.globals` in both `TOGTools/.luarc.json` and `!TOGT/.luarc.json` so the new timer call resolves cleanly under the Lua LSP.

---

## [v0.1.0] (2026-05-04) - Initial Release

### New Features

- **Project scaffolding** — TOC files for all WoW versions (Vanilla 11508, TBC 20505, Wrath 30405, Cata 40402, Mists 50503, Retail 110207/120001/120000), `.pkgmeta` with BigWigs packager config, `.luarc.json` (Lua 5.1 LSP), `.markdownlint.json`, and `.github/workflows/release.yml` for tag-triggered CurseForge releases.
- **Core addon** — `TOGTools.lua`: AceAddon-3.0 instance with AceConsole-3.0, AceEvent-3.0, AceHook-3.0 mixins; AceDB-3.0 schema (`char` scope); version detection flags (`gv.isVanilla`, `gv.isTBC`, `gv.isWrath`, `gv.isCata`, `gv.isMists`, `gv.isClassic`, `gv.isRetail`, `gv.isRetail120Plus`).
- **Slash commands** — `SlashCommands.lua`: all `/togt` command registration and handling in a dedicated file, separate from core addon logic. Subcommands: `/togt` (toggle window), `/togt np` (Name Prefix tab), `/togt settings` (open settings), `/togt vc` (version check).
- **Tabbed main window** — `GUI/MainWindow.lua`: dynamic AceGUI Frame + TabGroup; tabs auto-populate from `addon.modules` sorted alphabetically by `label`; per-tab locked/unlocked window sizing via `WINDOW_SIZE`; ESC-to-close proxy; position persistence via AceDB.
- **Name Prefix module** — `Modules/NamePrefix/NamePrefix.lua` + `GUI/NamePrefixTab.lua`: wraps `ChatEdit_SendText` (Classic) or hooks `EventRegistry` (Retail 12.0+) to prepend a configurable nickname to outgoing chat messages. Settings: enable/disable, nickname, format string with live preview, per-channel toggles (Guild, Officer, Party, Raid, Instance, custom channel), skip-exclamation option, suppress-if-char-name option. Per-character DB scope. Skips messages beginning with `/` (slash commands/macros) unconditionally.
- **Minimap button** — `GUI/MinimapButton.lua`: LibDataBroker-1.1 launcher + LibDBIcon-1.0 registration. Left-click toggles the main window; right-click opens the native addon settings panel. Icon: `textures/ToGTools_PH_MMB.tga`. Button show/hide state and position persisted in `DB.char`.
- **Addon settings panel** — `GUI/Settings.lua`: AceConfig-3.0 options table registered under ESC → Interface → Addons → TOG Tools (native Blizzard panel). `addon:OpenSettings()` uses `Settings.OpenToCategory` (Classic Era 1.15+ / Retail 10+), `InterfaceOptionsFrame_OpenToCategory` (older builds), or `InterfaceOptionsFrame:Show()` as a final fallback — detected by API presence, not version flag.
- **VersionCheck-1.0 integration** — `VC:Enable(Ace)` called on all non-Retail versions in `OnInitialize`; `/togt vc` broadcasts a guild-wide version check and prints responses after 21 seconds.
- **Embedded libs** — `libs/LibDataBroker-1.1.lua`, `libs/LibDBIcon-1.0.lua` (single-file embeds, copied from TOGProfessionMaster).
- **MIT License** — `LICENSE` file added; referenced in `.pkgmeta` via `license-output`.
- **CurseForge project** — Project ID `1533830` set in `.pkgmeta` and all TOC files.
- **Governance files** — `.github/copilot-instructions.md` and `CLAUDE.md` with full project rules (module pattern, commit/tag process, changelog format, HTML doc rules, no-tag rule).

### Bug Fixes

- **Name Prefix not reaching other players** — `hooksecurefunc` fires after `SendChatMessage` has already been called. Fixed by wrapping `ChatEdit_SendText` directly (`local orig = ...; ChatEdit_SendText = function(...) ... return orig(...) end`) so the prefix is applied before the message is sent. Location: `Modules/NamePrefix/NamePrefix.lua`.
- **Macros/slash commands being prefixed** — Added an unconditional early-return in `ModifyMessage` when the message starts with `/`. Location: `Modules/NamePrefix/NamePrefix.lua`.

---
